diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..2e5e5c88e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+.env
+.git
+.venv
+Dockerfile
+.vscode
+README.md
diff --git a/.github/workflows/continousint.yaml b/.github/workflows/continousint.yaml
new file mode 100644
index 000000000..f2f6b2f89
--- /dev/null
+++ b/.github/workflows/continousint.yaml
@@ -0,0 +1,18 @@
+name: Continuous Integration
+on:
+ push:
+ paths-ignore:
+ - 'README.md'
+
+ pull_request:
+ paths-ignore:
+ - 'README.md'
+
+jobs:
+ build:
+ name: Build and test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: docker build --tag todo-app:test --target test .
+ - run: docker run todo-app:test
\ No newline at end of file
diff --git a/Architecturediagrams/C4diagram.drawio b/Architecturediagrams/C4diagram.drawio
new file mode 100644
index 000000000..207ad989e
--- /dev/null
+++ b/Architecturediagrams/C4diagram.drawio
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Architecturediagrams/ComponentDiagram.drawio.png b/Architecturediagrams/ComponentDiagram.drawio.png
new file mode 100644
index 000000000..f8e055ba0
Binary files /dev/null and b/Architecturediagrams/ComponentDiagram.drawio.png differ
diff --git a/Architecturediagrams/Contextdiagram.drawio.png b/Architecturediagrams/Contextdiagram.drawio.png
new file mode 100644
index 000000000..0c00f7e31
Binary files /dev/null and b/Architecturediagrams/Contextdiagram.drawio.png differ
diff --git a/Architecturediagrams/codediagram.drawio.png b/Architecturediagrams/codediagram.drawio.png
new file mode 100644
index 000000000..655e8799c
Binary files /dev/null and b/Architecturediagrams/codediagram.drawio.png differ
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..0d4e27b59
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+FROM python AS base
+RUN pip install poetry
+WORKDIR /app
+COPY . .
+RUN poetry install
+ENTRYPOINT poetry run flask run --host=0.0.0.0
+
+
+FROM base as production
+ENV FLASK_DEBUG=false
+ENTRYPOINT poetry run flask run --host=0.0.0.0
+# Configure for production
+
+FROM base as development
+ENV FLASK_DEBUG=true
+ENTRYPOINT poetry run flask run --host=0.0.0.0
+# Configure for local development
+
+FROM base as test
+
+ENTRYPOINT poetry run pytest
diff --git a/README.md b/README.md
index e153c3396..750de35b0 100644
--- a/README.md
+++ b/README.md
@@ -52,3 +52,44 @@ You should see output similar to the following:
* Debugger PIN: 226-556-590
```
Now visit [`http://localhost:5000/`](http://localhost:5000/) in your web browser to view the app.
+
+```git remote -v ```
+tells us / lists our remotes
+
+
+### See below for how to build the two docker images
+```
+docker build --target development --tag todo-app:dev .
+docker build --target production --tag todo-app:prod .
+```
+### Command to run the production container
+```
+docker run --env-file .env -it -p 5001:5000 todo-app:prod
+```
+This command pases through the environment variables with the `--env-file` flag, and the `-it` flags make it easier to interact with the container (e.g. allowing us to shut it down with ctrl+c from our host terminal). With the `-p` flag, the app can be accessed at the address `http://localhost:5001`.
+
+### Command to run dev container
+```
+docker run --env-file .env -it -p 5001:5000 --mount "type=bind,source=$(pwd)/todo_app,target=/app/todo_app" todo-app:dev
+```
+Bind mount is miroring a folder and the folder in this instance is the todo app folder allowing changes to the code without having to rebuild the container. Running dev changes without having to access the container
+
+### Running Pytest
+```bash
+poetry run pytest
+```
+
+
+### Azure Hosting
+The container image that is deployed on Azure is hosted on Docker Hub at https://hub.docker.com/repository/docker/nashussain76/todo-app/general
+
+The website is hosted at https://nashusappservice.azurewebsites.net/
+
+To update the website you will need to run the following commands to build and push the updated container image:
+```Bash
+docker build --target production --tag nashussain76/todo-app:prod .
+docker push nashussain76/todo-app:prod
+```
+Next you will need to make a POST request to the webhook link provided on the App Service (under the Deployment Centre tab)> This will trigger Azure to pull the updated image from Docker Hub (link not provided as it includes credentials)
+
+
diff --git a/poetry.lock b/poetry.lock
index 26406b36e..072560de7 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "blinker"
version = "1.6.2"
description = "Fast, simple object-to-object and broadcast signaling"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -16,7 +15,6 @@ files = [
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -31,7 +29,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@@ -39,11 +36,24 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+[[package]]
+name = "exceptiongroup"
+version = "1.2.2"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
[[package]]
name = "flask"
version = "2.3.2"
description = "A simple framework for building complex web applications."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -67,7 +77,6 @@ dotenv = ["python-dotenv"]
name = "importlib-metadata"
version = "6.6.0"
description = "Read metadata from Python packages"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -83,11 +92,21 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
perf = ["ipython"]
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
[[package]]
name = "itsdangerous"
version = "2.1.2"
description = "Safely pass data to untrusted environments and back."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -99,7 +118,6 @@ files = [
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -117,7 +135,6 @@ i18n = ["Babel (>=2.7)"]
name = "markupsafe"
version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -141,6 +158,16 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@@ -173,11 +200,58 @@ files = [
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
+[[package]]
+name = "packaging"
+version = "24.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pytest"
+version = "8.2.2"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
+ {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=1.5,<2.0"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
[[package]]
name = "python-dotenv"
version = "0.14.0"
description = "Add .env support to your django/flask apps in development and deployments"
-category = "main"
optional = false
python-versions = "*"
files = [
@@ -188,11 +262,21 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+
[[package]]
name = "werkzeug"
version = "2.3.6"
description = "The comprehensive WSGI web application library."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -210,7 +294,6 @@ watchdog = ["watchdog (>=2.3)"]
name = "zipp"
version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -225,4 +308,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "d8d856f03434ab9857d0c9bd058ef199cc0438a15afe205c0fcd177e92d0691f"
+content-hash = "34187741501d30279611f3933c014bfa5ca2cb78f67a3d4911391cdcf774288d"
diff --git a/pyproject.toml b/pyproject.toml
index 4d9bb90f6..299d93979 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,7 @@ authors = []
python = "^3.8"
Flask = "^2.2.5"
python-dotenv = "^0.14.0"
+pytest = "^8.2.2"
[tool.poetry.dev-dependencies]
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/dummy2_test.py b/test/dummy2_test.py
new file mode 100644
index 000000000..db6ab3751
--- /dev/null
+++ b/test/dummy2_test.py
@@ -0,0 +1,10 @@
+def test_basic_multiplication():
+ # Given
+ num1 = 5
+ num2 = 10
+
+ # When
+ result = num1 * num2
+
+ # Then
+ assert result == 50
\ No newline at end of file
diff --git a/test/dummy_test.py b/test/dummy_test.py
new file mode 100644
index 000000000..83892bd9d
--- /dev/null
+++ b/test/dummy_test.py
@@ -0,0 +1,10 @@
+def test_that_basic_addition_works():
+ # Given
+ num1 = 2
+ num2 = 3
+
+ # When
+ result = num1 + num2
+
+ # Then
+ assert result == 5
\ No newline at end of file
diff --git a/todo_app/app.py b/todo_app/app.py
index d71780a32..0375806fc 100644
--- a/todo_app/app.py
+++ b/todo_app/app.py
@@ -1,11 +1,22 @@
-from flask import Flask
+from flask import Flask, redirect, render_template, request
from todo_app.flask_config import Config
+from todo_app.data.session_items import get_items, add_item
+
+
+
app = Flask(__name__)
app.config.from_object(Config())
@app.route('/')
def index():
- return 'Hello World!'
+ items = get_items()
+ return render_template('index.html', html_items = items)
+
+@app.route('/', methods = ["POST"])
+def new_todo():
+ todo = request.form.get("user todo")
+ item = add_item(todo)
+ return redirect('/')
\ No newline at end of file
diff --git a/todo_app/templates/index.html b/todo_app/templates/index.html
index d6f8d9c85..a7a167dd5 100644
--- a/todo_app/templates/index.html
+++ b/todo_app/templates/index.html
@@ -3,15 +3,24 @@
{% block content %}
-
To-Do App
+
Azure To-Do App
Just another to-do app.
Items
+
+ {% for item in html_items%}
+ -
+ {{ item.id }} - {{ item.status }} - {{item.title}}
+
+ {% endfor %}