mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feature/additional-db (#371)
* add support for setting db_url * fix tests * add db_username/password env variables * init db if super user doesn't exist * fix tests * fix tests * set SQLite default DB_URL * don't run tests on draft PRs * add lint/black tests * add test-all * spell check settings * black/flake8 * check format fail * new badges * rename workflow * fix formatting * remove white-space * test connection arguments for pg * format * add new values to template * format * remove old script * monkeypatch test db * working docker-compose for postgres * update docs * test pg workflow * format * add driver * install w/ poetry * setup container * change image * set database to localhost * update tests * set url * fix url path * disable cache * database init * bust cache * get by name Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
52e5e9da5d
commit
c196445e61
@ -8,12 +8,23 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- dev
|
- dev
|
||||||
|
types: [synchronize, opened, reopened, ready_for_review]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
env:
|
env:
|
||||||
PRODUCTION: false
|
PRODUCTION: false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_DB: mealie
|
||||||
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
steps:
|
steps:
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
# check-out repo and set-up python
|
# check-out repo and set-up python
|
||||||
@ -32,9 +43,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
virtualenvs-create: true
|
virtualenvs-create: true
|
||||||
virtualenvs-in-project: true
|
virtualenvs-in-project: true
|
||||||
# #----------------------------------------------
|
#----------------------------------------------
|
||||||
# # load cached venv if cache exists #! This Breaks Stuff
|
# load cached venv if cache exists
|
||||||
# #----------------------------------------------
|
#----------------------------------------------
|
||||||
# - name: Load cached venv
|
# - name: Load cached venv
|
||||||
# id: cached-poetry-dependencies
|
# id: cached-poetry-dependencies
|
||||||
# uses: actions/cache@v2
|
# uses: actions/cache@v2
|
||||||
@ -45,11 +56,22 @@ jobs:
|
|||||||
# install dependencies if cache does not exist
|
# install dependencies if cache does not exist
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: poetry install
|
run: |
|
||||||
|
poetry install
|
||||||
|
poetry add "psycopg2-binary==2.8.6"
|
||||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
# run test suite
|
# run test suite
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: Run tests
|
- name: Run Test Suite
|
||||||
run: |
|
run: |
|
||||||
poetry run pytest
|
make test-all
|
||||||
|
#----------------------------------------------
|
||||||
|
# run test suite
|
||||||
|
#----------------------------------------------
|
||||||
|
- name: Run Test Suite Postgres
|
||||||
|
env:
|
||||||
|
DB_ENGINE: postgres
|
||||||
|
POSTGRES_SERVER: localhost
|
||||||
|
run: |
|
||||||
|
make test-all
|
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@ -8,11 +8,21 @@
|
|||||||
"python.testing.pytestEnabled": true,
|
"python.testing.pytestEnabled": true,
|
||||||
"python.testing.autoTestDiscoverOnSaveEnabled": false,
|
"python.testing.autoTestDiscoverOnSaveEnabled": false,
|
||||||
"python.testing.pytestArgs": ["tests"],
|
"python.testing.pytestArgs": ["tests"],
|
||||||
"cSpell.enableFiletypes": ["!javascript", "!python"],
|
"cSpell.enableFiletypes": [
|
||||||
|
"!javascript",
|
||||||
|
"!python",
|
||||||
|
"!yaml"
|
||||||
|
],
|
||||||
"i18n-ally.localesPaths": "frontend/src/locales/messages",
|
"i18n-ally.localesPaths": "frontend/src/locales/messages",
|
||||||
"i18n-ally.sourceLanguage": "en-US",
|
"i18n-ally.sourceLanguage": "en-US",
|
||||||
"i18n-ally.enabledFrameworks": ["vue"],
|
"i18n-ally.enabledFrameworks": ["vue"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"cSpell.words": ["performant"],
|
"cSpell.words": [
|
||||||
|
"compression",
|
||||||
|
"hkotel",
|
||||||
|
"performant",
|
||||||
|
"postgres",
|
||||||
|
"webp"
|
||||||
|
],
|
||||||
"search.mode": "reuseEditor"
|
"search.mode": "reuseEditor"
|
||||||
}
|
}
|
||||||
|
16
Dockerfile
16
Dockerfile
@ -16,14 +16,11 @@ ENV POETRY_VERSION 1.1.6
|
|||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
gcc g++ \
|
gcc g++ \
|
||||||
curl \
|
curl \
|
||||||
python3-dev \
|
|
||||||
build-essential \
|
|
||||||
libssl-dev \
|
|
||||||
libffi-dev \
|
|
||||||
gnupg gnupg2 gnupg1 \
|
gnupg gnupg2 gnupg1 \
|
||||||
apt-transport-https \
|
apt-transport-https \
|
||||||
debian-archive-keyring \
|
debian-archive-keyring \
|
||||||
debian-keyring \
|
debian-keyring \
|
||||||
|
libpq-dev \
|
||||||
libwebp-dev \
|
libwebp-dev \
|
||||||
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | apt-key add - \
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | apt-key add - \
|
||||||
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee -a /etc/apt/sources.list.d/caddy-stable.list \
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee -a /etc/apt/sources.list.d/caddy-stable.list \
|
||||||
@ -35,12 +32,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
|
|
||||||
RUN pip install --no-cache-dir "poetry==$POETRY_VERSION"
|
RUN pip install --no-cache-dir "poetry==$POETRY_VERSION"
|
||||||
|
RUN pip install --no-cache-dir "psycopg2-binary==2.8.6"
|
||||||
|
|
||||||
#! Future
|
# project dependencies
|
||||||
# pip install --no-cache-dir "psycopg2-binary==2.8.6"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pyproject.toml /app/
|
COPY pyproject.toml poetry.lock /app/
|
||||||
|
RUN poetry config virtualenvs.create false
|
||||||
|
RUN poetry install --no-dev --no-interaction --no-ansi
|
||||||
|
|
||||||
COPY ./mealie /app/mealie
|
COPY ./mealie /app/mealie
|
||||||
RUN poetry config virtualenvs.create false \
|
RUN poetry config virtualenvs.create false \
|
||||||
&& poetry install --no-dev
|
&& poetry install --no-dev
|
||||||
@ -48,7 +47,6 @@ RUN poetry config virtualenvs.create false \
|
|||||||
#! Future
|
#! Future
|
||||||
# COPY ./alembic /app
|
# COPY ./alembic /app
|
||||||
# COPY alembic.ini /app
|
# COPY alembic.ini /app
|
||||||
|
|
||||||
COPY ./Caddyfile /app
|
COPY ./Caddyfile /app
|
||||||
COPY ./dev/data/templates /app/data/templates
|
COPY ./dev/data/templates /app/data/templates
|
||||||
|
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
[![Issues][issues-shield]][issues-url]
|
[![Issues][issues-shield]][issues-url]
|
||||||
[![MIT License][license-shield]][license-url]
|
[![MIT License][license-shield]][license-url]
|
||||||
[![Docker Pulls][docker-pull]][docker-pull]
|
[![Docker Pulls][docker-pull]][docker-pull]
|
||||||
|
[](https://www.codefactor.io/repository/github/hay-kot/mealie)
|
||||||
|
[](https://github.com/hay-kot/mealie/actions/workflows/dockerbuild.release.yml)
|
||||||
|
[](https://github.com/hay-kot/mealie/actions/workflows/pytest.yml)
|
||||||
|
[](https://github.com/hay-kot/mealie/actions/workflows/dockerbuild.dev.yml)
|
||||||
|
[](https://github.com/hay-kot/mealie/actions/workflows/pytest.yml)
|
||||||
|
|
||||||
<!-- PROJECT LOGO -->
|
<!-- PROJECT LOGO -->
|
||||||
<br />
|
<br />
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
"""
|
|
||||||
Helper script to download raw recipe data from a URL and dump it to disk.
|
|
||||||
The resulting files can be used as test input data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, json, pprint
|
|
||||||
import requests
|
|
||||||
import extruct
|
|
||||||
from scrape_schema_recipe import scrape_url
|
|
||||||
from w3lib.html import get_base_url
|
|
||||||
|
|
||||||
for url in sys.argv[1:]:
|
|
||||||
try:
|
|
||||||
data = scrape_url(url)[0]
|
|
||||||
slug = list(filter(None, url.split("/")))[-1]
|
|
||||||
filename = f"{slug}.json"
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
json.dump(data, f, indent=4, default=str)
|
|
||||||
print(f"Saved {filename}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error for {url}: {e}")
|
|
||||||
print("Trying extruct instead")
|
|
||||||
pp = pprint.PrettyPrinter(indent=2)
|
|
||||||
r = requests.get(url)
|
|
||||||
base_url = get_base_url(r.text, r.url)
|
|
||||||
data = extruct.extract(r.text, base_url=base_url)
|
|
||||||
pp.pprint(data)
|
|
@ -28,7 +28,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 9921:9000
|
- 9921:9000
|
||||||
environment:
|
environment:
|
||||||
db_type: sqlite
|
|
||||||
TZ: America/Anchorage # Specify Correct Timezone for Date/Time to line up correctly.
|
TZ: America/Anchorage # Specify Correct Timezone for Date/Time to line up correctly.
|
||||||
volumes:
|
volumes:
|
||||||
- ./dev/data:/app/dev/data
|
- ./dev/data:/app/dev/data
|
||||||
|
@ -9,4 +9,16 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 9090:80
|
- 9090:80
|
||||||
environment:
|
environment:
|
||||||
db_type: sqlite
|
DB_ENGINE: postgres # Optional: 'sqlite', 'postgres'
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_SERVER: postgres
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_DB: mealie
|
||||||
|
postgres:
|
||||||
|
container_name: postgres
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
@ -11,14 +11,22 @@
|
|||||||
#### Database
|
#### Database
|
||||||
Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data.
|
Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data.
|
||||||
|
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
- Fixed #332 - Language settings are saved for one browser
|
- Fixed #332 - Language settings are saved for one browser
|
||||||
- Fixes #281 - Slow Handling of Large Sets of Recipes
|
- Fixes #281 - Slow Handling of Large Sets of Recipes
|
||||||
|
- Fixed #356 - Shopping lists generate duplicate items
|
||||||
|
- Fixed #271 - Slow handling of larger data sets
|
||||||
|
|
||||||
## Features and Improvements
|
## Features and Improvements
|
||||||
- 'Dinner this week' shows a warning when no meal is planned yet
|
|
||||||
- 'Dinner today' shows a warning when no meal is planned yet
|
### Highlights
|
||||||
|
- Beta Support for Postgres! 🎉 See the getting started page for details
|
||||||
|
- Recipe Steps now support sections, assets, and additional settings.
|
||||||
|
- New Toolbox Page!
|
||||||
|
- Bulk assign categories and tags by keyword search
|
||||||
|
- Title case all Categories or Tags with 1 click
|
||||||
|
- Create/Rename/Delete Operations for Tags/Categories
|
||||||
|
- Remove Unused Categories or Tags with 1 click
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
- Images are now served up by the Caddy increase performance and offloading some loads from the API server
|
- Images are now served up by the Caddy increase performance and offloading some loads from the API server
|
||||||
@ -26,11 +34,10 @@
|
|||||||
- All images are now converted to .webp for better compression
|
- All images are now converted to .webp for better compression
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- New Toolbox Page!
|
- Updated recipe editor styles and moved notes to below the steps.
|
||||||
- Bulk assign categories and tags by keyword search
|
- Redesigned search bar
|
||||||
- Title case all Categories or Tags with 1 click
|
- 'Dinner this week' shows a warning when no meal is planned yet
|
||||||
- Create/Rename/Delete Operations for Tags/Categories
|
- 'Dinner today' shows a warning when no meal is planned yet
|
||||||
- Remove Unused Categories or Tags with 1 click
|
|
||||||
- More localization
|
- More localization
|
||||||
- Start date for Week is now selectable
|
- Start date for Week is now selectable
|
||||||
- Languages are now managed through Crowdin
|
- Languages are now managed through Crowdin
|
||||||
@ -41,8 +48,8 @@
|
|||||||
- Improved search layout on mobile
|
- Improved search layout on mobile
|
||||||
- Profile image now shown on all sidebars
|
- Profile image now shown on all sidebars
|
||||||
|
|
||||||
|
|
||||||
### Behind the Scenes
|
### Behind the Scenes
|
||||||
|
- Black and Flake8 now run as CI/CD checks
|
||||||
- New debian based docker image
|
- New debian based docker image
|
||||||
- Unified Sidebar Components
|
- Unified Sidebar Components
|
||||||
- Refactor UI components to fit Vue best practices (WIP)
|
- Refactor UI components to fit Vue best practices (WIP)
|
||||||
|
@ -9,11 +9,12 @@ We use github to host code, to track issues and feature requests, as well as acc
|
|||||||
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
|
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
|
||||||
|
|
||||||
1. Fork the repo and create your branch from `dev`.
|
1. Fork the repo and create your branch from `dev`.
|
||||||
2. Read the page in in [dev/dev-notes.md](https://github.com/hay-kot/mealie/blob/master/dev/dev-notes.md) to get an idea on where the project is at.
|
2. Checkout the Discord, the PRs page, or the Projects page to get an idea of what's already being worked on.
|
||||||
3. If you're interested on working on major changes please get in touch on discord and coordinate with other developers. No sense in doubling up on work if someones already on it.
|
3. If you're interested on working on major changes please get in touch on discord and coordinate with other developers. No sense in doubling up on work if someones already on it.
|
||||||
4. If you've changed APIs, update the documentation.
|
4. Once you've got an idea of what changes you want to make, create a draft PR as soon as you can to let us know what you're working on and how we can help!
|
||||||
5. Issue that pull request!
|
5. If you've changed APIs, update the documentation.
|
||||||
6. If you make changes to the dev branch reflect those changes in the dev/dev-notes.md to keep track of changes. Don't forget to add your name/handle/identifier!
|
6. Issue that pull request!
|
||||||
|
7. If you make changes to the dev branch reflect those changes in the active changelog to keep track of changes. Don't forget to add your name/handle/identifier!
|
||||||
|
|
||||||
## Any contributions you make will be under the MIT Software License
|
## Any contributions you make will be under the MIT Software License
|
||||||
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
# Guidelines
|
# Guidelines
|
||||||
|
|
||||||
TODO
|
## Python
|
||||||
|
|
||||||
|
## Vue
|
@ -33,17 +33,8 @@ Once the prerequisites are installed you can cd into the project base directory
|
|||||||
|
|
||||||
`make docker-prod` Builds docker-compose.yml to test for production
|
`make docker-prod` Builds docker-compose.yml to test for production
|
||||||
|
|
||||||
|
## Before you Commit!
|
||||||
|
|
||||||
## Trouble Shooting
|
Before you commit any changes on the backend/python side you'll want to run `make format` to format all the code with black. `make lint` to check with flake8, and `make test` to run pytests. You can also use `make test-all` to run both `lint` and `test`.
|
||||||
|
|
||||||
!!! Error "Symptom: Vue Development Server Wont Start"
|
|
||||||
**Error:** `TypeError: Cannot read property 'upgrade' of undefined`
|
|
||||||
|
|
||||||
**Solution:** You may be missing the `/frontend/.env.development.` The contents should be `VUE_APP_API_BASE_URL=http://127.0.0.1:9921`. This is a reference to proxy the the API requests from Vue to 127.0.0.1 at port 9921 where FastAPI should be running.
|
|
||||||
|
|
||||||
!!! Error "Symptom: FastAPI Development Server Wont Start"
|
|
||||||
**Error:** `RuntimeError: Directory '/app/dist' does not exist`
|
|
||||||
|
|
||||||
**Solution:** Create an empty /mealie/dist directory. This directory is served as static content by FastAPI. It is provided during the build process and may be missing in development.
|
|
||||||
|
|
||||||
Run into another issue? [Ask for help on discord](https://discord.gg/QuStdQGSGK)
|
Run into another issue? [Ask for help on discord](https://discord.gg/QuStdQGSGK)
|
@ -1,14 +0,0 @@
|
|||||||
# Usage
|
|
||||||
|
|
||||||
## Getting a Token
|
|
||||||
Bla Bla
|
|
||||||
|
|
||||||
## Key Components
|
|
||||||
### Recipe Extras
|
|
||||||
Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.
|
|
||||||
|
|
||||||
For example you could add `{"message": "Remember to thaw the chicken"}` to a recipe and use the webhooks built into mealie to send that message payload to a destination to be processed.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Have Ideas? Submit a PR!
|
|
@ -1,5 +1,5 @@
|
|||||||
# Installation
|
# Installation
|
||||||
To deploy docker on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose below you should be able to get a stack up and running easily by changing a few default values and deploying. Currently only SQLite is supported. Postrgres support is planned, however for most loads you may find SQLite performant enough.
|
To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose below you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with mealies automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish.
|
||||||
|
|
||||||
|
|
||||||
[Get Docker](https://docs.docker.com/get-docker/)
|
[Get Docker](https://docs.docker.com/get-docker/)
|
||||||
@ -10,22 +10,12 @@ To deploy docker on your local network it is highly recommended to use docker to
|
|||||||
- linux/arm/v7
|
- linux/arm/v7
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
|
|
||||||
!!! tip "Fatal Python error: init_interp_main: can't initialize time"
|
|
||||||
Some users experience an problem with running the linux/arm/v7 container on Raspberry Pi 4. This is not a problem with the Mealie container, but with a bug in the hosts Docker installation.
|
|
||||||
|
|
||||||
Update the host RP4 using [instructions](linuxserver/docker-papermerge#4 (comment)), summarized here:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
wget http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_armhf.deb
|
|
||||||
sudo dpkg -i libseccomp2_2.5.1-1_armhf.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start - Docker CLI
|
## Quick Start - Docker CLI
|
||||||
Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9925`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9925 and you'll should see mealie up and running!
|
Deployment with the Docker CLI can be done with `docker run` and specify the database type, in this case `sqlite`, setting the exposed port `9925`, mounting the current directory, and pull the latest image. After the image is up an running you can navigate to http://your.ip.addres:9925 and you'll should see mealie up and running!
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run \
|
docker run \
|
||||||
-e DB_TYPE='sqlite' \
|
|
||||||
-p 9925:80 \
|
-p 9925:80 \
|
||||||
-v `pwd`:'/app/data/' \
|
-v `pwd`:'/app/data/' \
|
||||||
hkotel/mealie:latest
|
hkotel/mealie:latest
|
||||||
@ -50,27 +40,68 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 9925:80
|
- 9925:80
|
||||||
environment:
|
environment:
|
||||||
DB_TYPE: sqlite
|
|
||||||
TZ: America/Anchorage
|
TZ: America/Anchorage
|
||||||
volumes:
|
volumes:
|
||||||
- ./mealie/data/:/app/data
|
- ./mealie/data/:/app/data
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker Compose with Postgres *(BETA)*
|
||||||
|
Postgres support was introduced in v0.5.0. At this point it should be used with caution and frequent backups.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.1"
|
||||||
|
services:
|
||||||
|
mealie:
|
||||||
|
container_name: mealie
|
||||||
|
image: hkotel/mealie:latest
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 9090:80
|
||||||
|
environment:
|
||||||
|
DB_ENGINE: postgres # Optional: 'sqlite', 'postgres'
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_SERVER: postgres
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_DB: mealie
|
||||||
|
postgres:
|
||||||
|
container_name: postgres
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: mealie
|
||||||
|
POSTGRES_USER: mealie
|
||||||
|
```
|
||||||
|
|
||||||
## Env Variables
|
## Env Variables
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| ---------------- | ------------------ | ----------------------------------------------------------------------------------- |
|
| ----------------- | ------------------ | ----------------------------------------------------------------------------------- |
|
||||||
| DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' |
|
|
||||||
| DEFAULT_GROUP | Home | The default group for users |
|
| DEFAULT_GROUP | Home | The default group for users |
|
||||||
| DEFAULT_EMAIL | changeme@email.com | The default username for the superuser |
|
| DEFAULT_EMAIL | changeme@email.com | The default username for the superuser |
|
||||||
| DEFAULT_PASSWORD | MyPassword | The default password for the superuser |
|
| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
|
||||||
|
| POSTGRES_USER | mealie | Postgres database user |
|
||||||
|
| POSTGRES_PASSWORD | mealie | Postgres database password |
|
||||||
|
| POSTGRES_SERVER | postgres | Postgres database server address |
|
||||||
|
| POSTGRES_PORT | 5432 | Postgres database port |
|
||||||
|
| POSTGRES_DB | mealie | Postgres database name |
|
||||||
| TOKEN_TIME | 2 | The time in hours that a login/auth token is valid |
|
| TOKEN_TIME | 2 | 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_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. |
|
| 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 |
|
| TZ | UTC | Must be set to get correct date/time on the server |
|
||||||
|
|
||||||
|
|
||||||
|
!!! tip "Fatal Python error: init_interp_main: can't initialize time"
|
||||||
|
Some users experience an problem with running the linux/arm/v7 container on Raspberry Pi 4. This is not a problem with the Mealie container, but with a bug in the hosts Docker installation.
|
||||||
|
|
||||||
|
Update the host RP4 using [instructions](linuxserver/docker-papermerge#4 (comment)), summarized here:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
wget http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_armhf.deb
|
||||||
|
sudo dpkg -i libseccomp2_2.5.1-1_armhf.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
@ -93,6 +124,11 @@ The Docker image provided by Mealie contains both the API and the html bundle in
|
|||||||
encode gzip
|
encode gzip
|
||||||
uri strip_suffix /
|
uri strip_suffix /
|
||||||
|
|
||||||
|
handle_path /api/recipes/image/* {
|
||||||
|
root * /app/data/img/
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
handle @proxied {
|
handle @proxied {
|
||||||
reverse_proxy http://127.0.0.1:9000
|
reverse_proxy http://127.0.0.1:9000
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
3
makefile
3
makefile
@ -44,8 +44,11 @@ format:
|
|||||||
poetry run black .
|
poetry run black .
|
||||||
|
|
||||||
lint: ## check style with flake8
|
lint: ## check style with flake8
|
||||||
|
poetry run black . --check
|
||||||
poetry run flake8 mealie tests
|
poetry run flake8 mealie tests
|
||||||
|
|
||||||
|
test-all: lint test ## Check Lint Format and Testing
|
||||||
|
|
||||||
setup: ## Setup Development Instance
|
setup: ## Setup Development Instance
|
||||||
poetry install && \
|
poetry install && \
|
||||||
cd frontend && \
|
cd frontend && \
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
import dotenv
|
import dotenv
|
||||||
from pydantic import BaseSettings, Field, validator
|
from pydantic import BaseSettings, Field, PostgresDsn, validator
|
||||||
|
|
||||||
APP_VERSION = "v0.5.0beta"
|
APP_VERSION = "v0.5.0beta"
|
||||||
DB_VERSION = "v0.5.0"
|
DB_VERSION = "v0.5.0"
|
||||||
@ -57,7 +57,6 @@ class AppDirectories:
|
|||||||
self.CHOWDOWN_DIR: Path = self.MIGRATION_DIR.joinpath("chowdown")
|
self.CHOWDOWN_DIR: Path = self.MIGRATION_DIR.joinpath("chowdown")
|
||||||
self.TEMPLATE_DIR: Path = data_dir.joinpath("templates")
|
self.TEMPLATE_DIR: Path = data_dir.joinpath("templates")
|
||||||
self.USER_DIR: Path = data_dir.joinpath("users")
|
self.USER_DIR: Path = data_dir.joinpath("users")
|
||||||
self.SQLITE_DIR: Path = data_dir.joinpath("db")
|
|
||||||
self.RECIPE_DATA_DIR: Path = data_dir.joinpath("recipes")
|
self.RECIPE_DATA_DIR: Path = data_dir.joinpath("recipes")
|
||||||
self.TEMP_DIR: Path = data_dir.joinpath(".temp")
|
self.TEMP_DIR: Path = data_dir.joinpath(".temp")
|
||||||
|
|
||||||
@ -70,7 +69,6 @@ class AppDirectories:
|
|||||||
self.DEBUG_DIR,
|
self.DEBUG_DIR,
|
||||||
self.MIGRATION_DIR,
|
self.MIGRATION_DIR,
|
||||||
self.TEMPLATE_DIR,
|
self.TEMPLATE_DIR,
|
||||||
self.SQLITE_DIR,
|
|
||||||
self.NEXTCLOUD_DIR,
|
self.NEXTCLOUD_DIR,
|
||||||
self.CHOWDOWN_DIR,
|
self.CHOWDOWN_DIR,
|
||||||
self.RECIPE_DATA_DIR,
|
self.RECIPE_DATA_DIR,
|
||||||
@ -84,6 +82,16 @@ class AppDirectories:
|
|||||||
app_dirs = AppDirectories(CWD, DATA_DIR)
|
app_dirs = AppDirectories(CWD, DATA_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def determine_sqlite_path(path=False, suffix=DB_VERSION) -> str:
|
||||||
|
global app_dirs
|
||||||
|
db_path = app_dirs.DATA_DIR.joinpath(f"mealie_{suffix}.db") # ! Temporary Until Alembic
|
||||||
|
|
||||||
|
if path:
|
||||||
|
return db_path
|
||||||
|
|
||||||
|
return "sqlite:///" + str(db_path.absolute())
|
||||||
|
|
||||||
|
|
||||||
class AppSettings(BaseSettings):
|
class AppSettings(BaseSettings):
|
||||||
global DATA_DIR
|
global DATA_DIR
|
||||||
PRODUCTION: bool = Field(True, env="PRODUCTION")
|
PRODUCTION: bool = Field(True, env="PRODUCTION")
|
||||||
@ -100,21 +108,29 @@ class AppSettings(BaseSettings):
|
|||||||
return "/redoc" if self.API_DOCS else None
|
return "/redoc" if self.API_DOCS else None
|
||||||
|
|
||||||
SECRET: str = determine_secrets(DATA_DIR, PRODUCTION)
|
SECRET: str = determine_secrets(DATA_DIR, PRODUCTION)
|
||||||
DATABASE_TYPE: str = Field("sqlite", env="DB_TYPE")
|
|
||||||
|
|
||||||
@validator("DATABASE_TYPE", pre=True)
|
DB_ENGINE: Optional[str] = None # Optional: 'sqlite', 'postgres'
|
||||||
def validate_db_type(cls, v: str) -> Optional[str]:
|
POSTGRES_USER: str = "mealie"
|
||||||
if v != "sqlite":
|
POSTGRES_PASSWORD: str = "mealie"
|
||||||
raise ValueError("Unable to determine database type. Acceptible options are 'sqlite'")
|
POSTGRES_SERVER: str = "postgres"
|
||||||
else:
|
POSTGRES_PORT: str = 5432
|
||||||
return v
|
POSTGRES_DB: str = "mealie"
|
||||||
|
|
||||||
# Used to Set SQLite File Version
|
DB_URL: Union[str, PostgresDsn] = None # Actual DB_URL is calculated with `assemble_db_connection`
|
||||||
SQLITE_FILE: Optional[Union[str, Path]]
|
|
||||||
|
|
||||||
@validator("SQLITE_FILE", pre=True)
|
@validator("DB_URL", pre=True)
|
||||||
def identify_sqlite_file(cls, v: str) -> Optional[str]:
|
def assemble_db_connection(cls, v: Optional[str], values: dict[str, Any]) -> Any:
|
||||||
return app_dirs.SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
|
engine = values.get("DB_ENGINE", "sqlite")
|
||||||
|
if engine == "postgres":
|
||||||
|
host = f"{values.get('POSTGRES_SERVER')}:{values.get('POSTGRES_PORT')}"
|
||||||
|
return PostgresDsn.build(
|
||||||
|
scheme="postgresql",
|
||||||
|
user=values.get("POSTGRES_USER"),
|
||||||
|
password=values.get("POSTGRES_PASSWORD"),
|
||||||
|
host=host,
|
||||||
|
path=f"/{values.get('POSTGRES_DB') or ''}",
|
||||||
|
)
|
||||||
|
return determine_sqlite_path()
|
||||||
|
|
||||||
DEFAULT_GROUP: str = "Home"
|
DEFAULT_GROUP: str = "Home"
|
||||||
DEFAULT_EMAIL: str = "changeme@email.com"
|
DEFAULT_EMAIL: str = "changeme@email.com"
|
||||||
|
@ -18,6 +18,7 @@ from mealie.schema.theme import SiteTheme
|
|||||||
from mealie.schema.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger()
|
logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
from mealie.core.config import settings
|
from mealie.core.config import settings
|
||||||
|
from mealie.db.models.db_session import sql_global_init
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from mealie.db.models.db_session import sql_global_init
|
SessionLocal = sql_global_init(settings.DB_URL)
|
||||||
|
|
||||||
sql_exists = True
|
|
||||||
|
|
||||||
sql_exists = settings.SQLITE_FILE.is_file()
|
|
||||||
SessionLocal = sql_global_init(settings.SQLITE_FILE)
|
|
||||||
|
|
||||||
|
|
||||||
def create_session() -> Session:
|
def create_session() -> Session:
|
||||||
|
@ -2,7 +2,7 @@ from mealie.core import root_logger
|
|||||||
from mealie.core.config import settings
|
from mealie.core.config import settings
|
||||||
from mealie.core.security import get_password_hash
|
from mealie.core.security import get_password_hash
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session, sql_exists
|
from mealie.db.db_setup import create_session
|
||||||
from mealie.schema.settings import SiteSettings
|
from mealie.schema.settings import SiteSettings
|
||||||
from mealie.schema.theme import SiteTheme
|
from mealie.schema.theme import SiteTheme
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@ -51,7 +51,9 @@ def default_user_init(session: Session):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if sql_exists:
|
session = create_session()
|
||||||
|
init_user = db.users.get(session, "1", "id")
|
||||||
|
if init_user:
|
||||||
print("Database Exists")
|
print("Database Exists")
|
||||||
else:
|
else:
|
||||||
print("Database Doesn't Exists, Initializing...")
|
print("Database Doesn't Exists, Initializing...")
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from mealie.db.models.model_base import SqlAlchemyBase
|
from mealie.db.models.model_base import SqlAlchemyBase
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
|
||||||
def sql_global_init(db_file: Path, check_thread=False):
|
def sql_global_init(db_url: str):
|
||||||
|
connect_args = {}
|
||||||
|
if "sqlite" in db_url:
|
||||||
|
connect_args["check_same_thread"] = False
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URL = "sqlite:///" + str(db_file.absolute())
|
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
|
||||||
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
|
|
||||||
|
|
||||||
engine = sa.create_engine(
|
|
||||||
SQLALCHEMY_DATABASE_URL,
|
|
||||||
echo=False,
|
|
||||||
connect_args={"check_same_thread": check_thread},
|
|
||||||
)
|
|
||||||
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import sqlalchemy.orm as orm
|
|||||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from mealie.db.models.recipe.category import Category, group2categories
|
from mealie.db.models.recipe.category import Category, group2categories
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
from mealie.core.config import settings
|
||||||
|
|
||||||
|
|
||||||
class WebhookURLModel(SqlAlchemyBase):
|
class WebhookURLModel(SqlAlchemyBase):
|
||||||
@ -57,5 +58,5 @@ class Group(SqlAlchemyBase, BaseMixins):
|
|||||||
def get_ref(session: Session, name: str):
|
def get_ref(session: Session, name: str):
|
||||||
item = session.query(Group).filter(Group.name == name).one_or_none()
|
item = session.query(Group).filter(Group.name == name).one_or_none()
|
||||||
if item is None:
|
if item is None:
|
||||||
item = session.query(Group).filter(Group.id == 1).one()
|
item = session.query(Group).filter(Group.name == settings.DEFAULT_GROUP).one()
|
||||||
return item
|
return item
|
||||||
|
@ -30,7 +30,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
|||||||
startDate = sa.Column(sa.Date)
|
startDate = sa.Column(sa.Date)
|
||||||
endDate = sa.Column(sa.Date)
|
endDate = sa.Column(sa.Date)
|
||||||
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
||||||
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id"))
|
group_id = sa.Column(sa.Integer, sa.ForeignKey("groups.id"))
|
||||||
group = orm.relationship("Group", back_populates="mealplans")
|
group = orm.relationship("Group", back_populates="mealplans")
|
||||||
|
|
||||||
def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None:
|
def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None:
|
||||||
|
@ -8,31 +8,31 @@ from sqlalchemy.orm import validates
|
|||||||
logger = root_logger.get_logger()
|
logger = root_logger.get_logger()
|
||||||
|
|
||||||
site_settings2categories = sa.Table(
|
site_settings2categories = sa.Table(
|
||||||
"site_settings2categoories",
|
"site_settings2categories",
|
||||||
SqlAlchemyBase.metadata,
|
SqlAlchemyBase.metadata,
|
||||||
sa.Column("sidebar_id", sa.Integer, sa.ForeignKey("site_settings.id")),
|
sa.Column("site_settings.id", sa.Integer, sa.ForeignKey("site_settings.id")),
|
||||||
sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")),
|
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||||
)
|
)
|
||||||
|
|
||||||
group2categories = sa.Table(
|
group2categories = sa.Table(
|
||||||
"group2categories",
|
"group2categories",
|
||||||
SqlAlchemyBase.metadata,
|
SqlAlchemyBase.metadata,
|
||||||
sa.Column("group_id", sa.Integer, sa.ForeignKey("groups.id")),
|
sa.Column("group_id", sa.Integer, sa.ForeignKey("groups.id")),
|
||||||
sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")),
|
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||||
)
|
)
|
||||||
|
|
||||||
recipes2categories = sa.Table(
|
recipes2categories = sa.Table(
|
||||||
"recipes2categories",
|
"recipes2categories",
|
||||||
SqlAlchemyBase.metadata,
|
SqlAlchemyBase.metadata,
|
||||||
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
|
||||||
sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")),
|
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||||
)
|
)
|
||||||
|
|
||||||
custom_pages2categories = sa.Table(
|
custom_pages2categories = sa.Table(
|
||||||
"custom_pages2categories",
|
"custom_pages2categories",
|
||||||
SqlAlchemyBase.metadata,
|
SqlAlchemyBase.metadata,
|
||||||
sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")),
|
sa.Column("custom_page_id", sa.Integer, sa.ForeignKey("custom_pages.id")),
|
||||||
sa.Column("category_id", sa.String, sa.ForeignKey("categories.id")),
|
sa.Column("category_id", sa.Integer, sa.ForeignKey("categories.id")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,11 +10,7 @@ class SiteSettings(SqlAlchemyBase, BaseMixins):
|
|||||||
id = sa.Column(sa.Integer, primary_key=True)
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
language = sa.Column(sa.String)
|
language = sa.Column(sa.String)
|
||||||
first_day_of_week = sa.Column(sa.Integer)
|
first_day_of_week = sa.Column(sa.Integer)
|
||||||
categories = orm.relationship(
|
categories = orm.relationship("Category", secondary=site_settings2categories, single_parent=True)
|
||||||
"Category",
|
|
||||||
secondary=site_settings2categories,
|
|
||||||
single_parent=True,
|
|
||||||
)
|
|
||||||
show_recent = sa.Column(sa.Boolean, default=True)
|
show_recent = sa.Column(sa.Boolean, default=True)
|
||||||
cards_per_section = sa.Column(sa.Integer)
|
cards_per_section = sa.Column(sa.Integer)
|
||||||
|
|
||||||
@ -44,11 +40,7 @@ class CustomPage(SqlAlchemyBase, BaseMixins):
|
|||||||
position = sa.Column(sa.Integer, nullable=False)
|
position = sa.Column(sa.Integer, nullable=False)
|
||||||
name = sa.Column(sa.String, nullable=False)
|
name = sa.Column(sa.String, nullable=False)
|
||||||
slug = sa.Column(sa.String, nullable=False)
|
slug = sa.Column(sa.String, nullable=False)
|
||||||
categories = orm.relationship(
|
categories = orm.relationship("Category", secondary=custom_pages2categories, single_parent=True)
|
||||||
"Category",
|
|
||||||
secondary=custom_pages2categories,
|
|
||||||
single_parent=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, session=None, name=None, slug=None, position=0, categories=[], *args, **kwargs) -> None:
|
def __init__(self, session=None, name=None, slug=None, position=0, categories=[], *args, **kwargs) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -16,7 +16,7 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||||||
full_name = Column(String, index=True)
|
full_name = Column(String, index=True)
|
||||||
email = Column(String, unique=True, index=True)
|
email = Column(String, unique=True, index=True)
|
||||||
password = Column(String)
|
password = Column(String)
|
||||||
group_id = Column(String, ForeignKey("groups.id"))
|
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||||
group = orm.relationship("Group", back_populates="users")
|
group = orm.relationship("Group", back_populates="users")
|
||||||
admin = Column(Boolean, default=False)
|
admin = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
@ -18,8 +18,7 @@ async def get_debug_info(current_user=Depends(get_current_user)):
|
|||||||
demo_status=settings.IS_DEMO,
|
demo_status=settings.IS_DEMO,
|
||||||
api_port=settings.API_PORT,
|
api_port=settings.API_PORT,
|
||||||
api_docs=settings.API_DOCS,
|
api_docs=settings.API_DOCS,
|
||||||
db_type=settings.DATABASE_TYPE,
|
db_url=settings.DB_URL,
|
||||||
sqlite_file=settings.SQLITE_FILE,
|
|
||||||
default_group=settings.DEFAULT_GROUP,
|
default_group=settings.DEFAULT_GROUP,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,6 +11,5 @@ class AppInfo(CamelModel):
|
|||||||
class DebugInfo(AppInfo):
|
class DebugInfo(AppInfo):
|
||||||
api_port: int
|
api_port: int
|
||||||
api_docs: bool
|
api_docs: bool
|
||||||
db_type: str
|
db_url: Path
|
||||||
sqlite_file: Path
|
|
||||||
default_group: str
|
default_group: str
|
||||||
|
13
template.env
13
template.env
@ -8,16 +8,19 @@ DEFAULT_PASSWORD=MyPassword
|
|||||||
# Determines Production Mode, This will set the directory path to use for data storage
|
# Determines Production Mode, This will set the directory path to use for data storage
|
||||||
PRODUCTION=False
|
PRODUCTION=False
|
||||||
|
|
||||||
# API Port for Pythong Server
|
# API Port for Python Server
|
||||||
API_PORT=9000
|
API_PORT=9000
|
||||||
|
|
||||||
# Exposes /docs and /redoc on the server
|
# Exposes /docs and /redoc on the server
|
||||||
API_DOCS=True
|
API_DOCS=True
|
||||||
|
|
||||||
# Sets the Database type to use. Currently the only supported options is 'sqlite'
|
# Sets the Database type to use. Note that in order for Postgres URI to be created, you must set DB_ENGINE=postgres
|
||||||
DB_TYPE=sqlite
|
DB_ENGINE=sqlite # Optional: 'sqlite', 'postgres'
|
||||||
|
POSTGRES_USER=mealie
|
||||||
# Sets the token expiration time in hours.
|
POSTGRES_PASSWORD=mealie
|
||||||
|
POSTGRES_SERVER=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=mealie
|
||||||
TOKEN_TIME=24
|
TOKEN_TIME=24
|
||||||
|
|
||||||
# NOT USED
|
# NOT USED
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
from mealie.core.config import app_dirs, settings
|
from tests.pre_test import DB_URL, settings # isort:skip
|
||||||
|
|
||||||
# ! I don't like it either!
|
|
||||||
SQLITE_FILE = app_dirs.SQLITE_DIR.joinpath("test.db")
|
|
||||||
SQLITE_FILE.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
settings.SQLITE_FILE = SQLITE_FILE
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from mealie.app import app
|
from mealie.app import app
|
||||||
from mealie.db.db_setup import generate_session, sql_global_init
|
from mealie.db.db_setup import SessionLocal, generate_session
|
||||||
from mealie.db.init_db import init_db
|
from mealie.db.init_db import main
|
||||||
from pytest import fixture
|
from pytest import fixture
|
||||||
|
|
||||||
from tests.app_routes import AppRoutes
|
from tests.app_routes import AppRoutes
|
||||||
from tests.test_config import TEST_DATA
|
from tests.test_config import TEST_DATA
|
||||||
from tests.utils.recipe_data import build_recipe_store, get_raw_no_image, get_raw_recipe
|
from tests.utils.recipe_data import build_recipe_store, get_raw_no_image, get_raw_recipe
|
||||||
|
|
||||||
TestSessionLocal = sql_global_init(SQLITE_FILE, check_thread=False)
|
main()
|
||||||
init_db(TestSessionLocal())
|
|
||||||
|
|
||||||
|
|
||||||
def override_get_db():
|
def override_get_db():
|
||||||
try:
|
try:
|
||||||
db = TestSessionLocal()
|
db = SessionLocal()
|
||||||
yield db
|
yield db
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
@ -38,7 +31,7 @@ def api_client():
|
|||||||
|
|
||||||
yield TestClient(app)
|
yield TestClient(app)
|
||||||
|
|
||||||
SQLITE_FILE.unlink()
|
DB_URL.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
|
Binary file not shown.
Binary file not shown.
8
tests/pre_test.py
Normal file
8
tests/pre_test.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from mealie.core.config import determine_sqlite_path, settings
|
||||||
|
|
||||||
|
DB_URL = determine_sqlite_path(path=True, suffix="test")
|
||||||
|
DB_URL.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
if settings.DB_ENGINE != "postgres":
|
||||||
|
# Monkeypatch Database Testing
|
||||||
|
settings.DB_URL = determine_sqlite_path(path=False, suffix="test")
|
@ -1,22 +1,25 @@
|
|||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
|
||||||
from mealie.core.config import CWD, DATA_DIR, AppDirectories, AppSettings, determine_data_dir, determine_secrets
|
from mealie.core.config import CWD, DATA_DIR, AppDirectories, AppSettings, determine_data_dir, determine_secrets
|
||||||
|
|
||||||
|
|
||||||
def test_default_settings(monkeypatch):
|
def test_default_settings(monkeypatch):
|
||||||
monkeypatch.delenv("DEFAULT_GROUP", raising=False)
|
monkeypatch.delenv("DEFAULT_GROUP", raising=False)
|
||||||
monkeypatch.delenv("DEFAULT_PASSWORD", raising=False)
|
monkeypatch.delenv("DEFAULT_PASSWORD", raising=False)
|
||||||
|
monkeypatch.delenv("POSTGRES_USER", raising=False)
|
||||||
|
monkeypatch.delenv("POSTGRES_PASSWORD", raising=False)
|
||||||
|
monkeypatch.delenv("DEFAULT_PASSWORD", raising=False)
|
||||||
monkeypatch.delenv("API_PORT", raising=False)
|
monkeypatch.delenv("API_PORT", raising=False)
|
||||||
monkeypatch.delenv("API_DOCS", raising=False)
|
monkeypatch.delenv("API_DOCS", raising=False)
|
||||||
monkeypatch.delenv("DB_TYPE", raising=False)
|
|
||||||
monkeypatch.delenv("IS_DEMO", raising=False)
|
monkeypatch.delenv("IS_DEMO", raising=False)
|
||||||
|
|
||||||
app_settings = AppSettings()
|
app_settings = AppSettings()
|
||||||
|
|
||||||
assert app_settings.DEFAULT_GROUP == "Home"
|
assert app_settings.DEFAULT_GROUP == "Home"
|
||||||
assert app_settings.DEFAULT_PASSWORD == "MyPassword"
|
assert app_settings.DEFAULT_PASSWORD == "MyPassword"
|
||||||
assert app_settings.DATABASE_TYPE == "sqlite"
|
assert app_settings.POSTGRES_USER == "mealie"
|
||||||
|
assert app_settings.POSTGRES_PASSWORD == "mealie"
|
||||||
assert app_settings.API_PORT == 9000
|
assert app_settings.API_PORT == 9000
|
||||||
assert app_settings.API_DOCS is True
|
assert app_settings.API_DOCS is True
|
||||||
assert app_settings.IS_DEMO is False
|
assert app_settings.IS_DEMO is False
|
||||||
@ -28,6 +31,8 @@ def test_default_settings(monkeypatch):
|
|||||||
def test_non_default_settings(monkeypatch):
|
def test_non_default_settings(monkeypatch):
|
||||||
monkeypatch.setenv("DEFAULT_GROUP", "Test Group")
|
monkeypatch.setenv("DEFAULT_GROUP", "Test Group")
|
||||||
monkeypatch.setenv("DEFAULT_PASSWORD", "Test Password")
|
monkeypatch.setenv("DEFAULT_PASSWORD", "Test Password")
|
||||||
|
monkeypatch.setenv("POSTGRES_USER", "mealie-test")
|
||||||
|
monkeypatch.setenv("POSTGRES_PASSWORD", "mealie-test")
|
||||||
monkeypatch.setenv("API_PORT", "8000")
|
monkeypatch.setenv("API_PORT", "8000")
|
||||||
monkeypatch.setenv("API_DOCS", "False")
|
monkeypatch.setenv("API_DOCS", "False")
|
||||||
|
|
||||||
@ -35,6 +40,8 @@ def test_non_default_settings(monkeypatch):
|
|||||||
|
|
||||||
assert app_settings.DEFAULT_GROUP == "Test Group"
|
assert app_settings.DEFAULT_GROUP == "Test Group"
|
||||||
assert app_settings.DEFAULT_PASSWORD == "Test Password"
|
assert app_settings.DEFAULT_PASSWORD == "Test Password"
|
||||||
|
assert app_settings.POSTGRES_USER == "mealie-test"
|
||||||
|
assert app_settings.POSTGRES_PASSWORD == "mealie-test"
|
||||||
assert app_settings.API_PORT == 8000
|
assert app_settings.API_PORT == 8000
|
||||||
assert app_settings.API_DOCS is False
|
assert app_settings.API_DOCS is False
|
||||||
|
|
||||||
@ -42,11 +49,17 @@ def test_non_default_settings(monkeypatch):
|
|||||||
assert app_settings.DOCS_URL is None
|
assert app_settings.DOCS_URL is None
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_database(monkeypatch):
|
def test_default_connection_args(monkeypatch):
|
||||||
monkeypatch.setenv("DB_TYPE", "nonsense")
|
monkeypatch.setenv("DB_ENGINE", "sqlite")
|
||||||
|
app_settings = AppSettings()
|
||||||
|
assert re.match(r"sqlite:////.*mealie/dev/data/mealie_v0.5.0.db", app_settings.DB_URL)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Unable to determine database type. Acceptible options are 'sqlite'"):
|
|
||||||
AppSettings()
|
def test_pg_connection_args(monkeypatch):
|
||||||
|
monkeypatch.setenv("DB_ENGINE", "postgres")
|
||||||
|
monkeypatch.setenv("POSTGRES_SERVER", "postgres")
|
||||||
|
app_settings = AppSettings()
|
||||||
|
assert app_settings.DB_URL == "postgresql://mealie:mealie@postgres:5432/mealie"
|
||||||
|
|
||||||
|
|
||||||
def test_secret_generation(tmp_path):
|
def test_secret_generation(tmp_path):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user