diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..54e6276
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+# Ignore .git
+.git
+
+# Ignore Test and Coverage Outputs
+.htmlcov
+.coverage
+tests/.pytest*
+
+# Ignore Configuration and JS/CSS source Files
+config/
+
+# Ignore Databases
+*.db
+
+# Ignore node_packages
+node_modules/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..83634bc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+FROM node:lts-alpine AS builder
+WORKDIR /app
+
+COPY sigl sigl
+COPY src src
+COPY Makefile ./
+COPY package.json ./
+COPY tailwind.config.js ./
+
+RUN apk add build-base \
+ && npm install \
+ && make css
+
+FROM python:3.8-slim-buster
+
+RUN groupadd -g 5151 sigl \
+ && adduser --disabled-password --uid 5151 --gid 5151 sigl
+
+WORKDIR /home/sigl
+
+RUN apt-get update \
+ && apt-get -y upgrade \
+ && apt-get -y install --no-install-recommends build-essential \
+ libpq-dev libmariadbclient-dev
+
+COPY requirements.txt ./
+RUN python -m venv venv \
+ && venv/bin/pip install -r requirements.txt \
+ && venv/bin/pip install gunicorn
+
+COPY sigl sigl
+COPY migrations migrations
+COPY docker/* ./
+COPY pyproject.toml ./
+
+COPY --from=builder /app/static ./static
+
+RUN mkdir -p /var/lib/sigl \
+ && chown -R sigl:sigl /var/lib/sigl ./ \
+ && chmod +x docker-entry.sh
+
+USER sigl
+EXPOSE 5151
+ENTRYPOINT [ "/home/sigl/docker-entry.sh" ]
+CMD [ "sigl" ]
diff --git a/Makefile b/Makefile
index bd48c82..e246aa2 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,12 @@ db-downgrade :
lint :
poetry run flake8
+requirements.txt : poetry.lock
+ poetry export -f requirements.txt --without-hashes -o requirements.txt
+
+requirements-dev.txt : poetry.lock
+ poetry export --dev -f requirements.txt --without-hashes -o requirements-dev.txt
+
serve :
poetry run python -m sigl
@@ -42,6 +48,8 @@ test-x :
test-wip :
poetry run python -m pytest tests -m wip
-.PHONY : db-init db-migrate db-upgrad db-downgrade \
+.PHONY : css \
+ db-init db-migrate db-upgrad db-downgrade \
lint shell serve \
- test test-wip test-x
\ No newline at end of file
+ requirements.txt requirements-dev.txt \
+ test test-wip test-x
diff --git a/docker/config.py b/docker/config.py
new file mode 100644
index 0000000..ee8d225
--- /dev/null
+++ b/docker/config.py
@@ -0,0 +1,24 @@
+"""Sigl Docker Default Configuration.
+
+Simple Grocery List (Sigl) | sigl.app
+Copyright (c) 2022 Asymworks, LLC. All Rights Reserved.
+"""
+
+# Session and Token Keys
+APP_SESSION_KEY = 'sigl-docker-session-key'
+APP_TOKEN_KEY = 'sigl-docker-token-key'
+
+# Database Settings
+DB_DRIVER = 'sqlite'
+DB_FILE = '/var/lib/sigl/sigl-test.db'
+
+# Mail Configuration
+MAIL_ENABLED = False
+
+# Logging Configuration
+LOGGING_DEST = 'wsgi'
+LOGGING_LEVEL = 'debug'
+LOGGING_FORMAT = '[%(asctime)s] [%(remote_addr)s - %(url)s] %(levelname)s in %(module)s: %(message)s'
+LOGGING_BACKTRACE = False
+SOCKETIO_LOGGING = True
+ENGINEIO_LOGGING = True
diff --git a/docker/docker-entry.sh b/docker/docker-entry.sh
new file mode 100644
index 0000000..2445193
--- /dev/null
+++ b/docker/docker-entry.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+
+export FLASK_APP=sigl.factory
+export FLASK_ENV=production
+export SIGL_CONFIG=${SIGL_CONFIG:-/home/sigl/config.py}
+export SIGL_PORT=${SIGL_PORT:-5151}
+
+source venv/bin/activate
+
+if [ "$1" = 'sigl' ]; then
+ exec gunicorn -k eventlet -b :${SIGL_PORT} --access-logfile - --error-logfile - sigl.wsgi:app
+fi
+
+if [ "$1" = 'db' ]; then
+ if [ "$2" = 'downgrade' ]; then
+ exec flask db downgrade
+ else
+ exec flask db upgrade
+ fi
+
+ exit 0
+fi
+
+exec "$@"
diff --git a/poetry.lock b/poetry.lock
index 6d67de6..58bfc72 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,6 +1,6 @@
[[package]]
name = "alembic"
-version = "1.8.0"
+version = "1.8.1"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
@@ -84,7 +84,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
-version = "6.4.1"
+version = "6.4.2"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@@ -95,30 +95,26 @@ toml = ["tomli"]
[[package]]
name = "dnspython"
-version = "2.2.1"
+version = "1.16.0"
description = "DNS toolkit"
category = "main"
optional = false
-python-versions = ">=3.6,<4.0"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
-dnssec = ["cryptography (>=2.6,<37.0)"]
-curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"]
-doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"]
-idna = ["idna (>=2.1,<4.0)"]
-trio = ["trio (>=0.14,<0.20)"]
-wmi = ["wmi (>=1.5.1,<2.0.0)"]
+DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"]
+IDNA = ["idna (>=2.1)"]
[[package]]
name = "eventlet"
-version = "0.33.1"
+version = "0.30.2"
description = "Highly concurrent networking library"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
-dnspython = ">=1.15.0"
+dnspython = ">=1.15.0,<2.0.0"
greenlet = ">=0.3"
six = ">=1.10.0"
@@ -137,7 +133,7 @@ pyflakes = ">=2.4.0,<2.5.0"
[[package]]
name = "flask"
-version = "2.1.2"
+version = "2.1.3"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
@@ -154,18 +150,6 @@ Werkzeug = ">=2.0"
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
-[[package]]
-name = "flask-cors"
-version = "3.0.10"
-description = "A Flask extension adding a decorator for CORS support"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-Flask = ">=0.9"
-Six = "*"
-
[[package]]
name = "flask-mail"
version = "0.9.1"
@@ -310,23 +294,6 @@ category = "main"
optional = false
python-versions = ">=3.7"
-[[package]]
-name = "marshmallow"
-version = "3.17.0"
-description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-packaging = ">=17.0"
-
-[package.extras]
-dev = ["pytest", "pytz", "simplejson", "mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)", "tox"]
-docs = ["sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.8)"]
-lint = ["mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)"]
-tests = ["pytest", "pytz", "simplejson"]
-
[[package]]
name = "mccabe"
version = "0.6.1"
@@ -339,7 +306,7 @@ python-versions = "*"
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.6"
@@ -386,7 +353,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.6.8"
@@ -503,20 +470,20 @@ watchdog = ["watchdog"]
[[package]]
name = "zipp"
-version = "3.8.0"
+version = "3.8.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
+docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "9a061ae1f8fa92a76f3d49706e062795362cdeb6f9b6067105f7dc6227e66018"
+content-hash = "3679bc9fc39e1e9f7160045f491258dfb6fce1b57b2b7963f11a636ee26106d9"
[metadata.files]
alembic = []
@@ -533,17 +500,16 @@ codespell = [
]
colorama = []
coverage = []
-dnspython = []
+dnspython = [
+ {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"},
+ {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"},
+]
eventlet = []
flake8 = [
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
]
flask = []
-flask-cors = [
- {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
- {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
-]
flask-mail = [
{file = "Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"},
]
@@ -561,7 +527,6 @@ itsdangerous = []
jinja2 = []
mako = []
markupsafe = []
-marshmallow = []
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
diff --git a/pyproject.toml b/pyproject.toml
index 4c847eb..b2f11c0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,23 +7,21 @@ license = "BSD-3-Clause"
[tool.poetry.dependencies]
python = "^3.9"
-Flask = "^2.1.2"
-Flask-SQLAlchemy = "^2.5.1"
-Flask-Mail = "^0.9.1"
-Flask-SocketIO = "^5.2.0"
-eventlet = "^0.33.1"
-Flask-Cors = "^3.0.10"
alembic = "^1.8.0"
-SQLAlchemy = "^1.4.39"
+eventlet = "0.30.2"
+Flask = "^2.1.2"
+Flask-Mail = "^0.9.1"
Flask-Migrate = "^3.1.0"
-marshmallow = "^3.17.0"
+Flask-SocketIO = "^5.2.0"
+Flask-SQLAlchemy = "^2.5.1"
+SQLAlchemy = "^1.4.39"
[tool.poetry.dev-dependencies]
+codespell = "^2.1.0"
coverage = "^6.4.1"
flake8 = "^4.0.1"
-pytest = "^7.1.2"
isort = "^5.10.1"
-codespell = "^2.1.0"
+pytest = "^7.1.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..4cd8e71
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,40 @@
+alembic==1.8.1; python_version >= "3.7"
+atomicwrites==1.4.1; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0"
+attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
+bidict==0.22.0; python_version >= "3.7"
+blinker==1.4
+click==8.1.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+codespell==2.1.0; python_version >= "3.5"
+colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and platform_system == "Windows" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" and platform_system == "Windows"
+coverage==6.4.2; python_version >= "3.7"
+dnspython==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
+eventlet==0.30.2
+flake8==4.0.1; python_version >= "3.6"
+flask-mail==0.9.1
+flask-migrate==3.1.0; python_version >= "3.6"
+flask-socketio==5.2.0; python_version >= "3.6"
+flask-sqlalchemy==2.5.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
+flask==2.1.3; python_version >= "3.7"
+greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.5.0"
+importlib-metadata==4.12.0; python_version < "3.10" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7")
+iniconfig==1.1.1; python_version >= "3.7"
+isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0"
+itsdangerous==2.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+jinja2==3.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+mako==1.2.1; python_version >= "3.7"
+markupsafe==2.1.1; python_version >= "3.7"
+mccabe==0.6.1; python_version >= "3.6"
+packaging==21.3; python_version >= "3.7"
+pluggy==1.0.0; python_version >= "3.7"
+py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
+pycodestyle==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
+pyflakes==2.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
+pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7"
+pytest==7.1.2; python_version >= "3.7"
+python-engineio==4.3.3; python_version >= "3.6"
+python-socketio==5.7.0; python_version >= "3.6"
+six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0"
+sqlalchemy==1.4.39; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
+tomli==2.0.1; python_version >= "3.7"
+werkzeug==2.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+zipp==3.8.1; python_version < "3.10" and python_version >= "3.7"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..753862b
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,24 @@
+alembic==1.8.1; python_version >= "3.7"
+bidict==0.22.0; python_version >= "3.7"
+blinker==1.4
+click==8.1.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0"
+dnspython==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
+eventlet==0.30.2
+flask-mail==0.9.1
+flask-migrate==3.1.0; python_version >= "3.6"
+flask-socketio==5.2.0; python_version >= "3.6"
+flask-sqlalchemy==2.5.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
+flask==2.1.3; python_version >= "3.7"
+greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.5.0"
+importlib-metadata==4.12.0; python_version < "3.10" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7")
+itsdangerous==2.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+jinja2==3.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+mako==1.2.1; python_version >= "3.7"
+markupsafe==2.1.1; python_version >= "3.7"
+python-engineio==4.3.3; python_version >= "3.6"
+python-socketio==5.7.0; python_version >= "3.6"
+six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0"
+sqlalchemy==1.4.39; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
+werkzeug==2.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
+zipp==3.8.1; python_version < "3.10" and python_version >= "3.7"
diff --git a/sigl/templates/base.html.j2 b/sigl/templates/base.html.j2
index dc615be..215b40e 100644
--- a/sigl/templates/base.html.j2
+++ b/sigl/templates/base.html.j2
@@ -8,7 +8,7 @@
{% block head_scripts %}{% endblock %}
{% if config['ENV'] == 'production' %}
-
+
{% else %}
{% endif %}
@@ -52,6 +52,10 @@