From c9027fa3eb5847041c4e62f0b419445b07c22855 Mon Sep 17 00:00:00 2001 From: Niklas Puller Date: Thu, 2 Apr 2020 22:07:57 +0200 Subject: [PATCH 01/46] Add Dockerfile --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..066beda --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.8 + +ARG INSTALL_PATH="/ito" + +ENV PIP_DISABLE_PIP_VERSION_CHECK=on +ENV FLASK_ENV "development" + +WORKDIR ${INSTALL_PATH} + +RUN pip install poetry + +COPY . ${INSTALL_PATH}/ + +RUN mv config.py.example config.py && \ + poetry config virtualenvs.create false && \ + poetry install + +CMD poetry run flask run --port=5001 \ No newline at end of file From f28f949077162e22c6b12ae0443f112b4bc7840e Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:34:44 +0200 Subject: [PATCH 02/46] Create functions for creating blueprints with database object --- app/routes/v0/cases.py | 33 +++++++++++++++++++-------------- app/routes/v0/contacts.py | 13 ++++++++----- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/routes/v0/cases.py b/app/routes/v0/cases.py index 47fe6b7..4b35a7d 100644 --- a/app/routes/v0/cases.py +++ b/app/routes/v0/cases.py @@ -1,20 +1,25 @@ from flask import Blueprint, request, Response, abort, current_app from app.model import ApiError -from app.persistence.db import insert_cases +from app.persistence.db import DBConnection from typing import Any, Optional, List -cases = Blueprint("v0.cases", __name__, url_prefix="/v0/cases") +def construct_cases_blueprint(dbConn: DBConnection): + cases = Blueprint("v0.cases", __name__, url_prefix="/v0/cases") -@cases.route("/report", methods=["POST"]) -def report() -> Response: - # TODO: check that user's infection has been verified - if not (current_app.config["DEBUG"] or current_app.config["TESTING"]): - return ApiError(501, "only available in dev and testing for now").as_response() - cases: Optional[List[Any]] = request.get_json() - if cases is None: - return ApiError( - 400, "please use the application/json content type", - ).as_response() - insert_cases(cases) - return Response(None, status=201) + @cases.route("/report", methods=["POST"]) + def report() -> Response: + # TODO: check that user's infection has been verified + if not (current_app.config["DEBUG"] or current_app.config["TESTING"]): + return ApiError( + 501, "only available in dev and testing for now" + ).as_response() + cases: Optional[List[Any]] = request.get_json() + if cases is None: + return ApiError( + 400, "please use the application/json content type", + ).as_response() + dbConn.insert_cases(cases) + return Response(None, status=201) + + return cases diff --git a/app/routes/v0/contacts.py b/app/routes/v0/contacts.py index 92d75a0..e139473 100644 --- a/app/routes/v0/contacts.py +++ b/app/routes/v0/contacts.py @@ -1,11 +1,14 @@ from flask import Blueprint, request, Response, abort, current_app from app.model import ApiError +from app.persistence.db import DBConnection -contacts = Blueprint("v0.contacts", __name__, url_prefix="/v0/contacts") +def construct_contacts_blueprint(dbConn: DBConnection): + contacts = Blueprint("v0.contacts", __name__, url_prefix="/v0/contacts") + @contacts.route("/report", methods=["POST"]) + def report() -> Response: + abort(501) + return Response(None, status=201) -@contacts.route("/report", methods=["POST"]) -def report() -> Response: - abort(501) - return Response(None, status=201) + return contacts From 2dc0d29ca537e5ad3cdac0417c8ce5c8b0df2c4f Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:35:51 +0200 Subject: [PATCH 03/46] Use pymondo instead of flask_pymongo flask_pymongo was causing problems because it automatically connects to mongoDB on localhost and ignored any URI passed to it. Also it features an older version of pymongo, which is totally unnecessary. --- app/persistence/db.py | 100 +++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/app/persistence/db.py b/app/persistence/db.py index 0124302..1a22568 100644 --- a/app/persistence/db.py +++ b/app/persistence/db.py @@ -1,5 +1,5 @@ from flask import Flask -from flask_pymongo import PyMongo # type: ignore +from pymongo import MongoClient from datetime import datetime, timedelta from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID @@ -7,53 +7,53 @@ import time from itertools import repeat -mongo = PyMongo() - -def insert_cases(cases: List[Any]) -> None: - mongo.db.cases.insert_many(cases) - - -def count_cases() -> int: - return int(mongo.db.cases.count_documents({})) - - -def insert_contact() -> None: - return - - -def random_time_in_the_past() -> datetime: - # FIXME: use a cryptographically secure RNG - now: datetime = datetime.now() - one_day_ago: datetime = now - timedelta(days=1) - noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) - # anything between 1 and 7 days ago - return one_day_ago - timedelta(minutes=noise_minutes) - - -def insert_random_cases(n: int) -> None: - for _ in repeat(None, n): - mongo.db.cases.insert( - { - "uuid": uuid4(), - "trust_level": 1, - "upload_timestamp": random_time_in_the_past(), - "lat": round(uniform(-90, 90), 1), - "lon": round(uniform(-180, 180), 1), - } - ) - - -def generate_random_cases(n: int) -> List[Any]: - cases: List[Any] = [] - for _ in repeat(None, n): - cases.append( - { - "uuid": uuid4(), - "trust_level": 1, - "upload_timestamp": random_time_in_the_past(), - "lat": round(uniform(-90, 90), 1), - "lon": round(uniform(-180, 180), 1), - } - ) - return cases +class DBConnection: + db: Any + + def __init__(self, hostUri: str): + client = MongoClient(hostUri) + self.db = client.ito + + def insert_cases(self, cases: List[Any]) -> None: + self.db.cases.insert_many(cases) + + def count_cases(self) -> int: + return int(self.db.cases.count_documents({})) + + def insert_contact(self) -> None: + return + + def random_time_in_the_past(self) -> datetime: + # FIXME: use a cryptographically secure RNG + now: datetime = datetime.now() + one_day_ago: datetime = now - timedelta(days=1) + noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) + # anything between 1 and 7 days ago + return one_day_ago - timedelta(minutes=noise_minutes) + + def insert_random_cases(self, n: int) -> None: + for _ in repeat(None, n): + self.db.cases.insert( + { + "uuid": uuid4(), + "trust_level": 1, + "upload_timestamp": self.random_time_in_the_past(), + "lat": round(uniform(-90, 90), 1), + "lon": round(uniform(-180, 180), 1), + } + ) + + def generate_random_cases(self, n: int) -> List[Any]: + cases: List[Any] = [] + for _ in repeat(None, n): + cases.append( + { + "uuid": uuid4(), + "trust_level": 1, + "upload_timestamp": self.random_time_in_the_past(), + "lat": round(uniform(-90, 90), 1), + "lon": round(uniform(-180, 180), 1), + } + ) + return cases From 44f6b7ce6e481c2968d56c63ba344abb716f93dc Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:36:41 +0200 Subject: [PATCH 04/46] Create routes with new blueprint creation functions and pass database to them --- app/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a1f21ba..69f9e35 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,17 +1,14 @@ -from flask import Flask -from app.persistence.db import mongo import os +from flask import Flask +from app.persistence.db import DBConnection -from .routes.v0 import cases -from .routes.v0 import contacts +from .routes.v0 import construct_cases_blueprint +from .routes.v0 import construct_contacts_blueprint def create_app() -> Flask: app = Flask(__name__) - app.register_blueprint(cases) - app.register_blueprint(contacts) - env = os.environ.get("FLASK_ENV", "production") if env == "production": app.config.from_object("config.ProductionConfig") @@ -23,5 +20,9 @@ def create_app() -> Flask: if mongo_uri is not None: app.config.update({"MONGO_URI": mongo_uri}) - mongo.init_app(app) + dbConn = DBConnection(os.environ.get("MONGO_URI")) + + app.register_blueprint(construct_cases_blueprint(dbConn)) + app.register_blueprint(construct_contacts_blueprint(dbConn)) + return app From e9bf2af8c06cee74ccaedbab2ba5f480e8a90525 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:37:57 +0200 Subject: [PATCH 05/46] Remove flask-pymongo as dependency --- poetry.lock | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1f9e361..bd44c8f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -85,18 +85,6 @@ dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxco docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dotenv = ["python-dotenv"] -[[package]] -category = "main" -description = "PyMongo support for Flask applications" -name = "flask-pymongo" -optional = false -python-versions = "*" -version = "2.3.0" - -[package.dependencies] -Flask = ">=0.11" -PyMongo = ">=3.3" - [[package]] category = "main" description = "Various helpers to pass data to untrusted environments and back." @@ -374,7 +362,7 @@ dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-i watchdog = ["watchdog"] [metadata] -content-hash = "7a9229ec53ecef396c14d86c79f73099b4c7d11862c8738f3ca913364888703e" +content-hash = "e509a6061a9d869b2553020abc393aa8ede2d9c0fbb3b1bf3fe73e974299f67f" python-versions = "^3.8" [metadata.files] @@ -406,10 +394,6 @@ flask = [ {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, ] -flask-pymongo = [ - {file = "Flask-PyMongo-2.3.0.tar.gz", hash = "sha256:620eb02dc8808a5fcb90f26cab6cba9d6bf497b15032ae3ca99df80366e33314"}, - {file = "Flask_PyMongo-2.3.0-py2.py3-none-any.whl", hash = "sha256:8a9577a2c6d00b49f21cb5a5a8d72561730364a2d745551a85349ab02f86fc73"}, -] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, From de0ec97d94475b4d05bfa9bb103f859fad59b541 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:38:09 +0200 Subject: [PATCH 06/46] Remove flask-pymongo as dependency and add pymongo --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c13e7bd..f393f15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,8 @@ authors = [] [tool.poetry.dependencies] python = "^3.8" Flask = "^1.1.1" -Flask-PyMongo = "^2.3.0" python-dateutil = "^2.8.1" +pymongo = "^3.10.1" [tool.poetry.dev-dependencies] black = "^19.10b0" From 8d240b9dd743c0b95a8a0a821971f353bd708518 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:38:32 +0200 Subject: [PATCH 07/46] Bind flask to 0.0.0.0 as host in Docker container --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 066beda..76ae2fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ RUN mv config.py.example config.py && \ poetry config virtualenvs.create false && \ poetry install -CMD poetry run flask run --port=5001 \ No newline at end of file +CMD poetry run flask run --host 0.0.0.0 --port=5001 \ No newline at end of file From 59f56b6281b844fef0a67ef82e9a3fb175f2219d Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:38:54 +0200 Subject: [PATCH 08/46] Add docker-compose file --- docker-compose.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..435b529 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3.7" +services: + mongodb: + image: mongo:latest + ports: + - 27017:27017 + networks: + - ito-network + + ito-upload: + build: + context: . + dockerfile: Dockerfile + ports: + - 5001:5001 + networks: + - ito-network + depends_on: + - mongodb + +networks: + ito-network: + +volumes: + db-data: \ No newline at end of file From 446fe60e6b90df25467dcf8f3e9cc3ff3082914f Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 14:39:04 +0200 Subject: [PATCH 09/46] Adapt tests to new structure --- tests/v0/test_cases.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index c395c17..14750a9 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,24 +1,26 @@ from flask import url_for from uuid import uuid4, UUID -from app.persistence.db import count_cases, generate_random_cases +from app.persistence.db import DBConnection import json from flask.testing import FlaskClient from flask import Response from datetime import datetime +import os def test_insert(client: FlaskClient): + dbConn = DBConnection(os.environ.get("MONGO_URI")) # TODO: actually test that case is inserted - prev_count: int = count_cases() + prev_count: int = dbConn.count_cases() n: int = 10 - cases: list = generate_random_cases(n) + cases: list = dbConn.generate_random_cases(n) res: Response = client.post( url_for("v0.cases.report"), data=json.dumps(cases, cls=CaseEncoder), content_type="application/json", ) assert res.status_code == 201 - assert count_cases() == (prev_count + n) + assert dbConn.count_cases() == (prev_count + n) # copied from https://stackoverflow.com/a/48159596/9926795 From 4304090bdc71884641c2d7661d6f9cbb906a94af Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 15:01:00 +0200 Subject: [PATCH 10/46] Use environment variables in Docker files --- Dockerfile | 1 + docker-compose.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 76ae2fd..705d71c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ARG INSTALL_PATH="/ito" ENV PIP_DISABLE_PIP_VERSION_CHECK=on ENV FLASK_ENV "development" +ENV MONGO_URI "mongodb://localhost:27017" WORKDIR ${INSTALL_PATH} diff --git a/docker-compose.yml b/docker-compose.yml index 435b529..00cbed9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: build: context: . dockerfile: Dockerfile + environment: + - MONGO_URI=mongodb://mongodb:27017 ports: - 5001:5001 networks: From b72437c299e6a27a6119809628ca74e9a3374768 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:36:50 +0200 Subject: [PATCH 11/46] Merge branch docker into merge-download --- app/persistence/db.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/persistence/db.py b/app/persistence/db.py index 1cbb763..04c75c0 100644 --- a/app/persistence/db.py +++ b/app/persistence/db.py @@ -23,8 +23,8 @@ def insert_cases(self, cases: List[Any]) -> None: def count_cases(self) -> int: return int(self.db.cases.count_documents({})) - def insert_contacts(contacts: List[Contact]) -> None: - mongo.db.contacts.insert_many(contacts) + def insert_contacts(self, contacts: List[Contact]) -> None: + self.db.contacts.insert_many(contacts) return def random_time_in_the_past(self) -> datetime: @@ -38,25 +38,26 @@ def random_time_in_the_past(self) -> datetime: def insert_random_cases(self, n: int) -> None: for _ in repeat(None, n): self.db.cases.insert( - { - "uuid": uuid4(), - "trust_level": 1, - "upload_timestamp": self.random_time_in_the_past(), - "lat": round(uniform(-90, 90), 1), - "lon": round(uniform(-180, 180), 1), - } + Case( + uuid4(), + round(uniform(-90, 90), 1), + round(uniform(-180, 180), 1), + 1, + self.random_time_in_the_past(), + ) ) - def generate_random_cases(self, n: int) -> List[Any]: - cases: List[Any] = [] + + def generate_random_cases(self, n: int) -> List[Case]: + cases: List[Case] = [] for _ in repeat(None, n): cases.append( - { - "uuid": uuid4(), - "trust_level": 1, - "upload_timestamp": self.random_time_in_the_past(), - "lat": round(uniform(-90, 90), 1), - "lon": round(uniform(-180, 180), 1), - } + Case( + uuid4(), + round(uniform(-90, 90), 1), + round(uniform(-180, 180), 1), + 1, + self.random_time_in_the_past(), + ) ) - return cases + return cases \ No newline at end of file From 4e019eb2686e42b13e5a9805813d6579309daa30 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:43:18 +0200 Subject: [PATCH 12/46] Change directory structure --- app.py | 4 --- app/__init__.py | 28 ----------------- app/routes/__init__.py | 0 conftest.py | 6 ++-- main.py | 32 ++++++++++++++++++++ {app/model => model}/__init__.py | 0 {app/model => model}/case.py | 0 {app/model => model}/contact.py | 0 {app/persistence => persistence}/__init__.py | 0 {app/persistence => persistence}/db.py | 7 ++--- routes/__init__.py | 1 + {app/routes => routes}/v0/__init__.py | 0 {app/routes => routes}/v0/cases.py | 6 ++-- {app/routes => routes}/v0/contacts.py | 6 ++-- tests/v0/test_cases.py | 2 +- 15 files changed, 45 insertions(+), 47 deletions(-) delete mode 100644 app.py delete mode 100644 app/routes/__init__.py create mode 100644 main.py rename {app/model => model}/__init__.py (100%) rename {app/model => model}/case.py (100%) rename {app/model => model}/contact.py (100%) rename {app/persistence => persistence}/__init__.py (100%) rename {app/persistence => persistence}/db.py (95%) create mode 100644 routes/__init__.py rename {app/routes => routes}/v0/__init__.py (100%) rename {app/routes => routes}/v0/cases.py (89%) rename {app/routes => routes}/v0/contacts.py (88%) diff --git a/app.py b/app.py deleted file mode 100644 index 86b7726..0000000 --- a/app.py +++ /dev/null @@ -1,4 +0,0 @@ -from flask import Flask -from app import create_app - -app: Flask = create_app() diff --git a/app/__init__.py b/app/__init__.py index 69f9e35..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,28 +0,0 @@ -import os -from flask import Flask -from app.persistence.db import DBConnection - -from .routes.v0 import construct_cases_blueprint -from .routes.v0 import construct_contacts_blueprint - - -def create_app() -> Flask: - app = Flask(__name__) - - env = os.environ.get("FLASK_ENV", "production") - if env == "production": - app.config.from_object("config.ProductionConfig") - elif env == "development": - app.config.from_object("config.DevelopmentConfig") - elif env == "testing": - app.config.from_object("config.TestingConfig") - mongo_uri = os.environ.get("MONGO_URI") - if mongo_uri is not None: - app.config.update({"MONGO_URI": mongo_uri}) - - dbConn = DBConnection(os.environ.get("MONGO_URI")) - - app.register_blueprint(construct_cases_blueprint(dbConn)) - app.register_blueprint(construct_contacts_blueprint(dbConn)) - - return app diff --git a/app/routes/__init__.py b/app/routes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/conftest.py b/conftest.py index e7343e3..ff910fd 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,8 @@ from flask import Flask import pytest # type: ignore - -from app import create_app +from main import create_app @pytest.fixture # type: ignore def app() -> Flask: - app = create_app() - return app + return create_app() diff --git a/main.py b/main.py new file mode 100644 index 0000000..38b592c --- /dev/null +++ b/main.py @@ -0,0 +1,32 @@ +from flask import Flask +import os +from flask import Flask +from persistence.db import DBConnection + +from routes.v0 import construct_cases_blueprint +from routes.v0 import construct_contacts_blueprint + + +def create_app() -> Flask: + app = Flask(__name__) + + env = os.environ.get("FLASK_ENV", "production") + if env == "production": + app.config.from_object("config.ProductionConfig") + elif env == "development": + app.config.from_object("config.DevelopmentConfig") + elif env == "testing": + app.config.from_object("config.TestingConfig") + mongo_uri = os.environ.get("MONGO_URI") + if mongo_uri is not None: + app.config.update({"MONGO_URI": mongo_uri}) + + dbConn = DBConnection(os.environ.get("MONGO_URI")) + + app.register_blueprint(construct_cases_blueprint(dbConn)) + app.register_blueprint(construct_contacts_blueprint(dbConn)) + + return app + + +app: Flask = create_app() diff --git a/app/model/__init__.py b/model/__init__.py similarity index 100% rename from app/model/__init__.py rename to model/__init__.py diff --git a/app/model/case.py b/model/case.py similarity index 100% rename from app/model/case.py rename to model/case.py diff --git a/app/model/contact.py b/model/contact.py similarity index 100% rename from app/model/contact.py rename to model/contact.py diff --git a/app/persistence/__init__.py b/persistence/__init__.py similarity index 100% rename from app/persistence/__init__.py rename to persistence/__init__.py diff --git a/app/persistence/db.py b/persistence/db.py similarity index 95% rename from app/persistence/db.py rename to persistence/db.py index 04c75c0..9aa2166 100644 --- a/app/persistence/db.py +++ b/persistence/db.py @@ -6,8 +6,8 @@ from random import randrange, uniform import time from itertools import repeat -from app.model.case import Case -from app.model.contact import Contact +from model.case import Case +from model.contact import Contact class DBConnection: @@ -47,7 +47,6 @@ def insert_random_cases(self, n: int) -> None: ) ) - def generate_random_cases(self, n: int) -> List[Case]: cases: List[Case] = [] for _ in repeat(None, n): @@ -60,4 +59,4 @@ def generate_random_cases(self, n: int) -> List[Case]: self.random_time_in_the_past(), ) ) - return cases \ No newline at end of file + return cases diff --git a/routes/__init__.py b/routes/__init__.py new file mode 100644 index 0000000..6599cd8 --- /dev/null +++ b/routes/__init__.py @@ -0,0 +1 @@ +from .v0 import * diff --git a/app/routes/v0/__init__.py b/routes/v0/__init__.py similarity index 100% rename from app/routes/v0/__init__.py rename to routes/v0/__init__.py diff --git a/app/routes/v0/cases.py b/routes/v0/cases.py similarity index 89% rename from app/routes/v0/cases.py rename to routes/v0/cases.py index da6ef4e..972293b 100644 --- a/app/routes/v0/cases.py +++ b/routes/v0/cases.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, Response, abort, current_app -from app.model import ApiError -from app.model.case import Case -from app.persistence.db import DBConnection +from model import ApiError +from model.case import Case +from persistence.db import DBConnection from typing import Any, Optional, List diff --git a/app/routes/v0/contacts.py b/routes/v0/contacts.py similarity index 88% rename from app/routes/v0/contacts.py rename to routes/v0/contacts.py index ee17159..bf67dba 100644 --- a/app/routes/v0/contacts.py +++ b/routes/v0/contacts.py @@ -1,8 +1,8 @@ from flask import Blueprint, request, Response, abort, current_app -from app.model import ApiError -from app.model.contact import Contact +from model import ApiError +from model.contact import Contact +from persistence.db import DBConnection from typing import Any, Optional, List -from app.persistence.db import DBConnection def construct_contacts_blueprint(dbConn: DBConnection): diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 8352d1b..9aa7dcb 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,6 +1,6 @@ from flask import url_for from uuid import uuid4, UUID -from app.persistence.db import DBConnection +from persistence.db import DBConnection import json from flask.testing import FlaskClient from flask import Response From 03100a64fb36c310cbd54a4877ee094a87afc121 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:45:05 +0200 Subject: [PATCH 13/46] Remove app directory --- app/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/__init__.py diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 From 9c6d3cc9e773d7129db710cb71db9fdac8e9b696 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:48:04 +0200 Subject: [PATCH 14/46] Rename persistence and model packages --- {persistence => db}/__init__.py | 0 {persistence => db}/db.py | 4 ++-- main.py | 2 +- models/__init__.py | 3 +++ model/__init__.py => models/api.py | 0 {model => models}/case.py | 0 {model => models}/contact.py | 0 routes/v0/cases.py | 6 +++--- routes/v0/contacts.py | 6 +++--- tests/v0/test_cases.py | 2 +- 10 files changed, 13 insertions(+), 10 deletions(-) rename {persistence => db}/__init__.py (100%) rename {persistence => db}/db.py (96%) create mode 100644 models/__init__.py rename model/__init__.py => models/api.py (100%) rename {model => models}/case.py (100%) rename {model => models}/contact.py (100%) diff --git a/persistence/__init__.py b/db/__init__.py similarity index 100% rename from persistence/__init__.py rename to db/__init__.py diff --git a/persistence/db.py b/db/db.py similarity index 96% rename from persistence/db.py rename to db/db.py index 9aa2166..b6ddb22 100644 --- a/persistence/db.py +++ b/db/db.py @@ -6,8 +6,8 @@ from random import randrange, uniform import time from itertools import repeat -from model.case import Case -from model.contact import Contact +from models.case import Case +from models.contact import Contact class DBConnection: diff --git a/main.py b/main.py index 38b592c..dd05738 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ from flask import Flask import os from flask import Flask -from persistence.db import DBConnection +from db.db import DBConnection from routes.v0 import construct_cases_blueprint from routes.v0 import construct_contacts_blueprint diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..0caf13a --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,3 @@ +from .api import * +from .case import * +from .contact import * \ No newline at end of file diff --git a/model/__init__.py b/models/api.py similarity index 100% rename from model/__init__.py rename to models/api.py diff --git a/model/case.py b/models/case.py similarity index 100% rename from model/case.py rename to models/case.py diff --git a/model/contact.py b/models/contact.py similarity index 100% rename from model/contact.py rename to models/contact.py diff --git a/routes/v0/cases.py b/routes/v0/cases.py index 972293b..f8800ca 100644 --- a/routes/v0/cases.py +++ b/routes/v0/cases.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, Response, abort, current_app -from model import ApiError -from model.case import Case -from persistence.db import DBConnection +from models.api import ApiError +from models.case import Case +from db.db import DBConnection from typing import Any, Optional, List diff --git a/routes/v0/contacts.py b/routes/v0/contacts.py index bf67dba..aed36d7 100644 --- a/routes/v0/contacts.py +++ b/routes/v0/contacts.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, Response, abort, current_app -from model import ApiError -from model.contact import Contact -from persistence.db import DBConnection +from models.api import ApiError +from models.contact import Contact +from db.db import DBConnection from typing import Any, Optional, List diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 9aa7dcb..0f62923 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,6 +1,6 @@ from flask import url_for from uuid import uuid4, UUID -from persistence.db import DBConnection +from db.db import DBConnection import json from flask.testing import FlaskClient from flask import Response From 41e97047389c0732149c683ecb4a1672096d0fa1 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:49:12 +0200 Subject: [PATCH 15/46] Format with black --- models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/__init__.py b/models/__init__.py index 0caf13a..b04252e 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,3 @@ from .api import * from .case import * -from .contact import * \ No newline at end of file +from .contact import * From d7573140e2385b332afcb01fd322a6b5cf2c3307 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sat, 4 Apr 2020 23:53:59 +0200 Subject: [PATCH 16/46] Move test helper functions out of db module --- db/db.py | 39 --------------------------------------- tests/v0/test_cases.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/db/db.py b/db/db.py index b6ddb22..b664be8 100644 --- a/db/db.py +++ b/db/db.py @@ -1,12 +1,7 @@ from flask import Flask from pymongo import MongoClient -from datetime import datetime, timedelta from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID -from random import randrange, uniform -import time -from itertools import repeat -from models.case import Case from models.contact import Contact @@ -26,37 +21,3 @@ def count_cases(self) -> int: def insert_contacts(self, contacts: List[Contact]) -> None: self.db.contacts.insert_many(contacts) return - - def random_time_in_the_past(self) -> datetime: - # FIXME: use a cryptographically secure RNG - now: datetime = datetime.now() - one_day_ago: datetime = now - timedelta(days=1) - noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) - # anything between 1 and 7 days ago - return one_day_ago - timedelta(minutes=noise_minutes) - - def insert_random_cases(self, n: int) -> None: - for _ in repeat(None, n): - self.db.cases.insert( - Case( - uuid4(), - round(uniform(-90, 90), 1), - round(uniform(-180, 180), 1), - 1, - self.random_time_in_the_past(), - ) - ) - - def generate_random_cases(self, n: int) -> List[Case]: - cases: List[Case] = [] - for _ in repeat(None, n): - cases.append( - Case( - uuid4(), - round(uniform(-90, 90), 1), - round(uniform(-180, 180), 1), - 1, - self.random_time_in_the_past(), - ) - ) - return cases diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 0f62923..d151c62 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,19 +1,50 @@ from flask import url_for +from typing import List from uuid import uuid4, UUID from db.db import DBConnection import json from flask.testing import FlaskClient from flask import Response from datetime import datetime +from itertools import repeat +from random import randrange, uniform +import time +from datetime import datetime, timedelta +from models.case import Case +from models.contact import Contact import os +def random_time_in_the_past() -> datetime: + # FIXME: use a cryptographically secure RNG + now: datetime = datetime.now() + one_day_ago: datetime = now - timedelta(days=1) + noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) + # anything between 1 and 7 days ago + return one_day_ago - timedelta(minutes=noise_minutes) + + +def generate_random_cases(n: int) -> List[Case]: + cases: List[Case] = [] + for _ in repeat(None, n): + cases.append( + Case( + uuid4(), + round(uniform(-90, 90), 1), + round(uniform(-180, 180), 1), + 1, + random_time_in_the_past(), + ) + ) + return cases + + def test_insert(client: FlaskClient): dbConn = DBConnection(os.environ.get("MONGO_URI")) # TODO: actually test that case is inserted prev_count: int = dbConn.count_cases() n: int = 10 - cases: list = dbConn.generate_random_cases(n) + cases: list = generate_random_cases(n) res: Response = client.post( url_for("v0.cases.report"), data=json.dumps([case.__dict__ for case in cases], cls=CaseEncoder), From 13df4cea6f2ed4c98fdee3110b64741bb3c9d4ab Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sun, 5 Apr 2020 00:32:29 +0200 Subject: [PATCH 17/46] Rename main.py to app.py --- main.py => app.py | 0 conftest.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename main.py => app.py (100%) diff --git a/main.py b/app.py similarity index 100% rename from main.py rename to app.py diff --git a/conftest.py b/conftest.py index ff910fd..59e1403 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,6 @@ from flask import Flask import pytest # type: ignore -from main import create_app +from app import create_app @pytest.fixture # type: ignore From 98e82c53e2eb6ec324fb493748a02554d7ad6d8a Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sun, 5 Apr 2020 00:32:51 +0200 Subject: [PATCH 18/46] Add get_cases function --- db/db.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/db/db.py b/db/db.py index b664be8..728fa9e 100644 --- a/db/db.py +++ b/db/db.py @@ -2,6 +2,10 @@ from pymongo import MongoClient from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID +from datetime import datetime, timedelta +import time +from random import randrange +from models.case import Case from models.contact import Contact @@ -21,3 +25,28 @@ def count_cases(self) -> int: def insert_contacts(self, contacts: List[Contact]) -> None: self.db.contacts.insert_many(contacts) return + + def get_cases( + self, uuid: UUID, lat: Optional[int] = None, lon: Optional[int] = None + ) -> Iterator[Case]: + # TODO: prevent timing attacks that could reveal if a UUID is present or not + conditions: Dict[str, Any] = {} + if lat is not None: + conditions["lat"] = lat + if lon is not None: + conditions["lon"] = lon + conditions["uuid"] = str(uuid) + + #if last_case is None: + # conditions["upload_timestamp"] = {"$gte": self.random_time_in_the_past()} + #else: + # conditions["upload_timestamp"] = {"$gte": last_case["upload_timestamp"]} + return (case for case in self.db.cases.find(conditions)) + + def random_time_in_the_past(self) -> datetime: + # FIXME: use a cryptographically secure RNG + now: datetime = datetime.now() + one_day_ago: datetime = now - timedelta(days=1) + noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) + # anything between 1 and 7 days ago + return one_day_ago - timedelta(minutes=noise_minutes) From f3ba7911c69574b4d5d7a2a55b0f6c9358f680f2 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sun, 5 Apr 2020 00:33:14 +0200 Subject: [PATCH 19/46] Add handler to get cases --- routes/v0/cases.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/routes/v0/cases.py b/routes/v0/cases.py index f8800ca..7cb7ecd 100644 --- a/routes/v0/cases.py +++ b/routes/v0/cases.py @@ -1,13 +1,41 @@ -from flask import Blueprint, request, Response, abort, current_app +from flask import Blueprint, request, Response, abort, current_app, jsonify from models.api import ApiError from models.case import Case from db.db import DBConnection -from typing import Any, Optional, List +from typing import Any, Optional, List, Union, Generator +from uuid import UUID +import json def construct_cases_blueprint(dbConn: DBConnection): cases = Blueprint("v0.cases", __name__, url_prefix="/v0/cases") + @cases.route("/", methods=["GET"], strict_slashes=False) + def index() -> Response: + lat: Union[Optional[float], int] = request.args.get("lat", type=float) + lon: Union[Optional[float], int] = request.args.get("lon", type=float) + uuid: Optional[UUID] = request.args.get("uuid", type=UUID) + + if uuid is None: + return ApiError(400, "No valid UUID for the requested query").as_response() + + try: + if lat is not None: + lat = round(lat) + if lon is not None: + lon = round(lon) + except ValueError: + abort(400) + + cases = dbConn.get_cases(uuid, lat=lat, lon=lon) + + def generate() -> Generator[str, None, None]: + for case in cases: + case_uuid = str(case["uuid"]) + yield case_uuid + "," + + return Response(generate(), mimetype="application/octet-stream") + @cases.route("/report", methods=["POST"]) def report() -> Response: # TODO: check that user's infection has been verified @@ -23,4 +51,4 @@ def report() -> Response: dbConn.insert_cases(cases) return Response(None, status=201) - return cases + return cases \ No newline at end of file From 00a967492038b77b9385b71ab55a3405fe21befd Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Sun, 5 Apr 2020 00:33:35 +0200 Subject: [PATCH 20/46] Move random_time_in_the_past to db module: --- tests/v0/test_cases.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index d151c62..9d2a351 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,30 +1,20 @@ from flask import url_for from typing import List from uuid import uuid4, UUID -from db.db import DBConnection +from db import DBConnection import json from flask.testing import FlaskClient from flask import Response -from datetime import datetime from itertools import repeat -from random import randrange, uniform -import time +from random import uniform from datetime import datetime, timedelta from models.case import Case from models.contact import Contact import os -def random_time_in_the_past() -> datetime: - # FIXME: use a cryptographically secure RNG - now: datetime = datetime.now() - one_day_ago: datetime = now - timedelta(days=1) - noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) - # anything between 1 and 7 days ago - return one_day_ago - timedelta(minutes=noise_minutes) - -def generate_random_cases(n: int) -> List[Case]: +def generate_random_cases(dbConn: DBConnection, n: int) -> List[Case]: cases: List[Case] = [] for _ in repeat(None, n): cases.append( @@ -33,7 +23,7 @@ def generate_random_cases(n: int) -> List[Case]: round(uniform(-90, 90), 1), round(uniform(-180, 180), 1), 1, - random_time_in_the_past(), + dbConn.random_time_in_the_past(), ) ) return cases @@ -44,7 +34,7 @@ def test_insert(client: FlaskClient): # TODO: actually test that case is inserted prev_count: int = dbConn.count_cases() n: int = 10 - cases: list = generate_random_cases(n) + cases: list = generate_random_cases(dbConn, n) res: Response = client.post( url_for("v0.cases.report"), data=json.dumps([case.__dict__ for case in cases], cls=CaseEncoder), From 0e5eaeaaa6f223a69b9d9884cb83fe6bf7845b6f Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 00:57:48 +0200 Subject: [PATCH 21/46] Format with black --- db/db.py | 4 ++-- routes/v0/cases.py | 2 +- tests/v0/test_cases.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/db/db.py b/db/db.py index 728fa9e..7382bcf 100644 --- a/db/db.py +++ b/db/db.py @@ -37,9 +37,9 @@ def get_cases( conditions["lon"] = lon conditions["uuid"] = str(uuid) - #if last_case is None: + # if last_case is None: # conditions["upload_timestamp"] = {"$gte": self.random_time_in_the_past()} - #else: + # else: # conditions["upload_timestamp"] = {"$gte": last_case["upload_timestamp"]} return (case for case in self.db.cases.find(conditions)) diff --git a/routes/v0/cases.py b/routes/v0/cases.py index 7cb7ecd..90d1284 100644 --- a/routes/v0/cases.py +++ b/routes/v0/cases.py @@ -51,4 +51,4 @@ def report() -> Response: dbConn.insert_cases(cases) return Response(None, status=201) - return cases \ No newline at end of file + return cases diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 9d2a351..967dd68 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -13,7 +13,6 @@ import os - def generate_random_cases(dbConn: DBConnection, n: int) -> List[Case]: cases: List[Case] = [] for _ in repeat(None, n): From ca82bf868c012b7265ad0bc0b9682c6c0a501995 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 14:29:01 +0200 Subject: [PATCH 22/46] Fix typing errors --- app.py | 4 ++-- db/db.py | 6 ++++-- routes/v0/cases.py | 4 ++-- routes/v0/contacts.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app.py b/app.py index dd05738..bbd5697 100644 --- a/app.py +++ b/app.py @@ -3,8 +3,8 @@ from flask import Flask from db.db import DBConnection -from routes.v0 import construct_cases_blueprint -from routes.v0 import construct_contacts_blueprint +from routes.v0.cases import construct_cases_blueprint +from routes.v0.contacts import construct_contacts_blueprint def create_app() -> Flask: diff --git a/db/db.py b/db/db.py index 7382bcf..0419c50 100644 --- a/db/db.py +++ b/db/db.py @@ -1,5 +1,5 @@ from flask import Flask -from pymongo import MongoClient +from pymongo import MongoClient # type: ignore from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID from datetime import datetime, timedelta @@ -12,7 +12,9 @@ class DBConnection: db: Any - def __init__(self, hostUri: str): + def __init__(self, hostUri: Optional[str]): + if hostUri is None: + hostUri = "mongodb://localhost:27017" client = MongoClient(hostUri) self.db = client.ito diff --git a/routes/v0/cases.py b/routes/v0/cases.py index 90d1284..55a11de 100644 --- a/routes/v0/cases.py +++ b/routes/v0/cases.py @@ -7,7 +7,7 @@ import json -def construct_cases_blueprint(dbConn: DBConnection): +def construct_cases_blueprint(dbConn: DBConnection) -> Blueprint: cases = Blueprint("v0.cases", __name__, url_prefix="/v0/cases") @cases.route("/", methods=["GET"], strict_slashes=False) @@ -31,7 +31,7 @@ def index() -> Response: def generate() -> Generator[str, None, None]: for case in cases: - case_uuid = str(case["uuid"]) + case_uuid = str(case.uuid) yield case_uuid + "," return Response(generate(), mimetype="application/octet-stream") diff --git a/routes/v0/contacts.py b/routes/v0/contacts.py index aed36d7..e078163 100644 --- a/routes/v0/contacts.py +++ b/routes/v0/contacts.py @@ -5,7 +5,7 @@ from typing import Any, Optional, List -def construct_contacts_blueprint(dbConn: DBConnection): +def construct_contacts_blueprint(dbConn: DBConnection) -> Blueprint: contacts = Blueprint("v0.contacts", __name__, url_prefix="/v0/contacts") @contacts.route("/report", methods=["POST"]) From 68655b5073c1ce617254a65f9fd4280aa9ab998b Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 14:31:48 +0200 Subject: [PATCH 23/46] Format with black --- db/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/db.py b/db/db.py index 0419c50..1cf1f2a 100644 --- a/db/db.py +++ b/db/db.py @@ -1,5 +1,5 @@ from flask import Flask -from pymongo import MongoClient # type: ignore +from pymongo import MongoClient # type: ignore from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID from datetime import datetime, timedelta From 15cd5df400c0b081975a883f7f98e099d216a37c Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 14:49:22 +0200 Subject: [PATCH 24/46] Install mongodb in actions pipeline --- .github/workflows/pythonapp.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 792c8d9..5c902e1 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -48,6 +48,9 @@ jobs: - name: Check types statically run: poetry run mypy . --strict + + - name: Install mongodb + run: sudo docker run --name mongo -d -p 27017:27017 mongo - name: Test with pytest run: poetry run pytest From d80baff15a914632b51bad8bd3be9c099dbf7cab Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 15:06:16 +0200 Subject: [PATCH 25/46] Try testing with localhost mongodb --- .github/workflows/pythonapp.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 5c902e1..29541dd 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -49,11 +49,8 @@ jobs: - name: Check types statically run: poetry run mypy . --strict - - name: Install mongodb - run: sudo docker run --name mongo -d -p 27017:27017 mongo - - name: Test with pytest run: poetry run pytest env: - MONGO_URI: ${{ secrets.MongoUri }} + MONGO_URI: mongodb://localhost:27017 FLASK_ENV: testing From 3cf5303096f08633e8eb683e2276a90e2c4cb7a4 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sun, 5 Apr 2020 15:08:39 +0200 Subject: [PATCH 26/46] Use GitHub secret --- .github/workflows/pythonapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 29541dd..c5c4e1f 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -52,5 +52,5 @@ jobs: - name: Test with pytest run: poetry run pytest env: - MONGO_URI: mongodb://localhost:27017 + MONGO_URI: ${{ secrets.MongoUri }} FLASK_ENV: testing From fd4423e07a06521ad70dc84c9a825c59aa089d98 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:26:48 +0200 Subject: [PATCH 27/46] Remove unused models and routes --- models/case.py | 35 ---------------------------- models/contact.py | 37 ----------------------------- routes/v0/cases.py | 54 ------------------------------------------- routes/v0/contacts.py | 26 --------------------- 4 files changed, 152 deletions(-) delete mode 100644 models/case.py delete mode 100644 models/contact.py delete mode 100644 routes/v0/cases.py delete mode 100644 routes/v0/contacts.py diff --git a/models/case.py b/models/case.py deleted file mode 100644 index 1f55cfb..0000000 --- a/models/case.py +++ /dev/null @@ -1,35 +0,0 @@ -import json -from uuid import UUID -from typing import List, Optional -from flask import Response -from datetime import datetime - -# https://confluence.ito-app.org/display/IN/API+Documentation - - -class Case: - uuid: UUID - lat: Optional[float] - lon: Optional[float] - trust_level: int - upload_timestamp: datetime - - def __init__( - self, - uuid: UUID, - lat: Optional[float], - lon: Optional[float], - trust_level: int, - upload_timestamp: datetime, - ): - self.uuid = uuid - self.lat = lat - self.lon = lon - self.trust_level = trust_level - self.upload_timestamp = upload_timestamp - - def toJSON(self) -> str: - return json.dumps(self, default=lambda o: o.__dict__) - - def as_response(self, code: int) -> Response: - return Response(self.toJSON(), status=code, mimetype="application/json") diff --git a/models/contact.py b/models/contact.py deleted file mode 100644 index 1cc887d..0000000 --- a/models/contact.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -from uuid import UUID -from typing import List, Optional -from flask import Response -from datetime import datetime - -# https://confluence.ito-app.org/display/IN/API+Documentation - - -class Contact: - uuid: UUID - lat: Optional[float] - lon: Optional[float] - trust_level: int - upload_timestamp: datetime - case_uuid: str - - def __init__( - self, - uuid: UUID, - lat: Optional[float], - lon: Optional[float], - trust_level: int, - upload_timestamp: datetime, - case_uuid: str, - ): - self.uuid = uuid - self.lat = lat - self.lon = lon - self.trust_level = trust_level - self.upload_timestamp = upload_timestamp - self.case_uuid = case_uuid - - def as_response(self, code: int) -> Response: - return Response( - json.dumps(self.__dict__), status=code, mimetype="application/json" - ) diff --git a/routes/v0/cases.py b/routes/v0/cases.py deleted file mode 100644 index 55a11de..0000000 --- a/routes/v0/cases.py +++ /dev/null @@ -1,54 +0,0 @@ -from flask import Blueprint, request, Response, abort, current_app, jsonify -from models.api import ApiError -from models.case import Case -from db.db import DBConnection -from typing import Any, Optional, List, Union, Generator -from uuid import UUID -import json - - -def construct_cases_blueprint(dbConn: DBConnection) -> Blueprint: - cases = Blueprint("v0.cases", __name__, url_prefix="/v0/cases") - - @cases.route("/", methods=["GET"], strict_slashes=False) - def index() -> Response: - lat: Union[Optional[float], int] = request.args.get("lat", type=float) - lon: Union[Optional[float], int] = request.args.get("lon", type=float) - uuid: Optional[UUID] = request.args.get("uuid", type=UUID) - - if uuid is None: - return ApiError(400, "No valid UUID for the requested query").as_response() - - try: - if lat is not None: - lat = round(lat) - if lon is not None: - lon = round(lon) - except ValueError: - abort(400) - - cases = dbConn.get_cases(uuid, lat=lat, lon=lon) - - def generate() -> Generator[str, None, None]: - for case in cases: - case_uuid = str(case.uuid) - yield case_uuid + "," - - return Response(generate(), mimetype="application/octet-stream") - - @cases.route("/report", methods=["POST"]) - def report() -> Response: - # TODO: check that user's infection has been verified - if not (current_app.config["DEBUG"] or current_app.config["TESTING"]): - return ApiError( - 501, "only available in dev and testing for now" - ).as_response() - cases: Optional[List[Any]] = request.get_json() - if cases is None: - return ApiError( - 400, "please use the application/json content type", - ).as_response() - dbConn.insert_cases(cases) - return Response(None, status=201) - - return cases diff --git a/routes/v0/contacts.py b/routes/v0/contacts.py deleted file mode 100644 index e078163..0000000 --- a/routes/v0/contacts.py +++ /dev/null @@ -1,26 +0,0 @@ -from flask import Blueprint, request, Response, abort, current_app -from models.api import ApiError -from models.contact import Contact -from db.db import DBConnection -from typing import Any, Optional, List - - -def construct_contacts_blueprint(dbConn: DBConnection) -> Blueprint: - contacts = Blueprint("v0.contacts", __name__, url_prefix="/v0/contacts") - - @contacts.route("/report", methods=["POST"]) - def report() -> Response: - abort(501) - if not (current_app.config["DEBUG"] or current_app.config["TESTING"]): - return ApiError( - 501, "only available in dev and testing for now" - ).as_response() - contacts: Optional[List[Contact]] = request.get_json() - if contacts is None: - return ApiError( - 400, "please use the application/json content type" - ).as_response() - dbConn.insert_contacts(contacts) - return Response(None, status=201) - - return contacts From f209855f9cbe7b8a56a59d0e72baf52d7841e3b8 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:27:05 +0200 Subject: [PATCH 28/46] Remove unused models and routes --- routes/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 routes/__init__.py diff --git a/routes/__init__.py b/routes/__init__.py deleted file mode 100644 index 6599cd8..0000000 --- a/routes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .v0 import * From 5763732bf090b1a527ecf4a2c05a3c669fba4049 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:27:13 +0200 Subject: [PATCH 29/46] Remove unused models and routes --- routes/v0/__init__.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 routes/v0/__init__.py diff --git a/routes/v0/__init__.py b/routes/v0/__init__.py deleted file mode 100644 index 91f6c29..0000000 --- a/routes/v0/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -__all__ = ["cases", "contacts"] - -from .cases import * -from .contacts import * From f9da7e1187fd47e419c2371f840b98b8450774e9 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:27:35 +0200 Subject: [PATCH 30/46] Rename ApiError to APIError --- models/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/api.py b/models/api.py index abecfea..42fed09 100644 --- a/models/api.py +++ b/models/api.py @@ -3,7 +3,7 @@ from typing import Optional -class ApiError: +class APIError: code: int message: str From 007d71217a1a5352a2c93ad216c2b47084f61d32 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:27:51 +0200 Subject: [PATCH 31/46] Remove unused database functions --- db/db.py | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/db/db.py b/db/db.py index 1cf1f2a..dc4e944 100644 --- a/db/db.py +++ b/db/db.py @@ -2,7 +2,7 @@ from pymongo import MongoClient # type: ignore from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID -from datetime import datetime, timedelta +from datetime import datetime import time from random import randrange from models.case import Case @@ -18,37 +18,8 @@ def __init__(self, hostUri: Optional[str]): client = MongoClient(hostUri) self.db = client.ito - def insert_cases(self, cases: List[Any]) -> None: - self.db.cases.insert_many(cases) + def insert_reportsig(self, reportsig: str, timestamp: datetime) -> None: + self.db.reportsigs.insert_one({"reportsig": reportsig, "timestamp": timestamp}) - def count_cases(self) -> int: - return int(self.db.cases.count_documents({})) - - def insert_contacts(self, contacts: List[Contact]) -> None: - self.db.contacts.insert_many(contacts) - return - - def get_cases( - self, uuid: UUID, lat: Optional[int] = None, lon: Optional[int] = None - ) -> Iterator[Case]: - # TODO: prevent timing attacks that could reveal if a UUID is present or not - conditions: Dict[str, Any] = {} - if lat is not None: - conditions["lat"] = lat - if lon is not None: - conditions["lon"] = lon - conditions["uuid"] = str(uuid) - - # if last_case is None: - # conditions["upload_timestamp"] = {"$gte": self.random_time_in_the_past()} - # else: - # conditions["upload_timestamp"] = {"$gte": last_case["upload_timestamp"]} - return (case for case in self.db.cases.find(conditions)) - - def random_time_in_the_past(self) -> datetime: - # FIXME: use a cryptographically secure RNG - now: datetime = datetime.now() - one_day_ago: datetime = now - timedelta(days=1) - noise_minutes: int = randrange(start=0, stop=60 * 24 * 6, step=1) - # anything between 1 and 7 days ago - return one_day_ago - timedelta(minutes=noise_minutes) + def get_reportsigs(self) -> List[str]: + return list(self.db.reportsigs.find({}, {"_id": False})) From d85cbc8bf0e095addaeafbee983cfa1c743eef94 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:28:13 +0200 Subject: [PATCH 32/46] Add handlers for /report route and simplify initialization --- app.py | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/app.py b/app.py index bbd5697..c8baa4c 100644 --- a/app.py +++ b/app.py @@ -1,32 +1,25 @@ from flask import Flask import os -from flask import Flask +import json +from flask import Flask, Response, request from db.db import DBConnection - -from routes.v0.cases import construct_cases_blueprint -from routes.v0.contacts import construct_contacts_blueprint - - -def create_app() -> Flask: - app = Flask(__name__) - - env = os.environ.get("FLASK_ENV", "production") - if env == "production": - app.config.from_object("config.ProductionConfig") - elif env == "development": - app.config.from_object("config.DevelopmentConfig") - elif env == "testing": - app.config.from_object("config.TestingConfig") - mongo_uri = os.environ.get("MONGO_URI") - if mongo_uri is not None: - app.config.update({"MONGO_URI": mongo_uri}) - - dbConn = DBConnection(os.environ.get("MONGO_URI")) - - app.register_blueprint(construct_cases_blueprint(dbConn)) - app.register_blueprint(construct_contacts_blueprint(dbConn)) - - return app - - -app: Flask = create_app() +from models.api import APIError + +app = Flask(__name__) +dbConn = DBConnection(os.environ.get("MONGO_URI")) + + +@app.route("/report", methods=["GET", "POST"]) +def report_handler(): + if request.method == "GET": + return Response( + json.dumps(dbConn.get_reportsigs()), 200, mimetype="application/json" + ) + else: + data = request.get_json() + if "reportsig" not in data or "timestamp" not in data: + return APIError(400, "Missing values in request").as_response() + reportsig = data["reportsig"] + timestamp = data["timestamp"] + dbConn.insert_reportsig(reportsig, timestamp) + return Response(None, 200) From 8095ebb388c8d2815057f3972f6729d8cd8894dd Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:39:09 +0200 Subject: [PATCH 33/46] Remove faulty imports --- db/db.py | 5 ++--- models/__init__.py | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/db/db.py b/db/db.py index dc4e944..24efe30 100644 --- a/db/db.py +++ b/db/db.py @@ -5,8 +5,6 @@ from datetime import datetime import time from random import randrange -from models.case import Case -from models.contact import Contact class DBConnection: @@ -19,7 +17,8 @@ def __init__(self, hostUri: Optional[str]): self.db = client.ito def insert_reportsig(self, reportsig: str, timestamp: datetime) -> None: - self.db.reportsigs.insert_one({"reportsig": reportsig, "timestamp": timestamp}) + self.db.reportsigs.insert_one( + {"reportsig": reportsig, "timestamp": timestamp}) def get_reportsigs(self) -> List[str]: return list(self.db.reportsigs.find({}, {"_id": False})) diff --git a/models/__init__.py b/models/__init__.py index b04252e..0a0e47b 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1 @@ from .api import * -from .case import * -from .contact import * From 815507c07e75eb6290adc5b871a16627e809a54d Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:39:38 +0200 Subject: [PATCH 34/46] Apply formatting --- conftest.py | 4 ++-- db/db.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/conftest.py b/conftest.py index 59e1403..2c18995 100644 --- a/conftest.py +++ b/conftest.py @@ -1,8 +1,8 @@ from flask import Flask import pytest # type: ignore -from app import create_app +from app import app as theapp @pytest.fixture # type: ignore def app() -> Flask: - return create_app() + return theapp diff --git a/db/db.py b/db/db.py index 24efe30..6299251 100644 --- a/db/db.py +++ b/db/db.py @@ -17,8 +17,7 @@ def __init__(self, hostUri: Optional[str]): self.db = client.ito def insert_reportsig(self, reportsig: str, timestamp: datetime) -> None: - self.db.reportsigs.insert_one( - {"reportsig": reportsig, "timestamp": timestamp}) + self.db.reportsigs.insert_one({"reportsig": reportsig, "timestamp": timestamp}) def get_reportsigs(self) -> List[str]: return list(self.db.reportsigs.find({}, {"_id": False})) From 542a796c92cf6723a197cbcb1bb292bbb493a57f Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:39:50 +0200 Subject: [PATCH 35/46] Rename report_handler to report --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index c8baa4c..a8516b1 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,7 @@ @app.route("/report", methods=["GET", "POST"]) -def report_handler(): +def report(): if request.method == "GET": return Response( json.dumps(dbConn.get_reportsigs()), 200, mimetype="application/json" From 8bbf0fe16e29fb9b34cb8c7eede9b119ff0c6643 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:40:19 +0200 Subject: [PATCH 36/46] Add provisional test for report handler --- tests/v0/test_cases.py | 57 +++++++----------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 967dd68..641168f 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -1,54 +1,15 @@ -from flask import url_for -from typing import List -from uuid import uuid4, UUID -from db import DBConnection import json +from datetime import datetime +from flask import url_for, Response from flask.testing import FlaskClient -from flask import Response -from itertools import repeat -from random import uniform -from datetime import datetime, timedelta -from models.case import Case -from models.contact import Contact -import os -def generate_random_cases(dbConn: DBConnection, n: int) -> List[Case]: - cases: List[Case] = [] - for _ in repeat(None, n): - cases.append( - Case( - uuid4(), - round(uniform(-90, 90), 1), - round(uniform(-180, 180), 1), - 1, - dbConn.random_time_in_the_past(), - ) - ) - return cases - - -def test_insert(client: FlaskClient): - dbConn = DBConnection(os.environ.get("MONGO_URI")) - # TODO: actually test that case is inserted - prev_count: int = dbConn.count_cases() - n: int = 10 - cases: list = generate_random_cases(dbConn, n) +def test_report_post(client: FlaskClient): + data = { + "reportsig": "teststr", + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } res: Response = client.post( - url_for("v0.cases.report"), - data=json.dumps([case.__dict__ for case in cases], cls=CaseEncoder), - content_type="application/json", + url_for(".report"), data=json.dumps(data), content_type="application/json" ) - assert res.status_code == 201 - assert dbConn.count_cases() == (prev_count + n) - - -# copied from https://stackoverflow.com/a/48159596/9926795 -class CaseEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, UUID): - # if the obj is uuid, we simply return the value of uuid - return obj.hex - if isinstance(obj, datetime): - return obj.isoformat() - return json.JSONEncoder.default(self, obj) + assert res.status_code == 200 From 37abc48bf423621ea75e79f2d6ba26866671c120 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Fri, 10 Apr 2020 23:45:16 +0200 Subject: [PATCH 37/46] Add return type for report function --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index a8516b1..f6c052b 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,7 @@ @app.route("/report", methods=["GET", "POST"]) -def report(): +def report() -> Response: if request.method == "GET": return Response( json.dumps(dbConn.get_reportsigs()), 200, mimetype="application/json" From 168a2895bd773a63099b542191657c305f62ef90 Mon Sep 17 00:00:00 2001 From: Michael Koeppl Date: Sat, 11 Apr 2020 00:05:26 +0200 Subject: [PATCH 38/46] Update GitHub actions workflow --- .github/workflows/pythonapp.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index c5c4e1f..7f9ebb5 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -48,9 +48,12 @@ jobs: - name: Check types statically run: poetry run mypy . --strict + + - name: Install mongodb + run: sudo docker run --name mongo -d -p 27017:27017 mongo - name: Test with pytest run: poetry run pytest env: - MONGO_URI: ${{ secrets.MongoUri }} + MONGO_URI: mongodb://localhost:27017 FLASK_ENV: testing From 8edf8a5910051ee70af11adf8944d7f06fa7f4e7 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 17:53:07 +0200 Subject: [PATCH 39/46] Create index for reportsig during initialization of DBConnection --- db/db.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/db.py b/db/db.py index 6299251..0b56271 100644 --- a/db/db.py +++ b/db/db.py @@ -1,5 +1,5 @@ from flask import Flask -from pymongo import MongoClient # type: ignore +from pymongo import MongoClient, DESCENDING # type: ignore from typing import Optional, Dict, Any, Iterator, List from uuid import uuid4, UUID from datetime import datetime @@ -16,6 +16,10 @@ def __init__(self, hostUri: Optional[str]): client = MongoClient(hostUri) self.db = client.ito + # Create index for reportsigs collection to make sure that reportsig + # is unique. + self.db.reportsigs.create_index([("reportsig", DESCENDING)], unique=True) + def insert_reportsig(self, reportsig: str, timestamp: datetime) -> None: self.db.reportsigs.insert_one({"reportsig": reportsig, "timestamp": timestamp}) From 818050c28c5da2347e9249fe69bb6f7d81a714fa Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 17:53:47 +0200 Subject: [PATCH 40/46] Return an API error if reportsig already exists --- app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index f6c052b..06c3ead 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ from flask import Flask import os import json +from pymongo.errors import DuplicateKeyError from flask import Flask, Response, request from db.db import DBConnection from models.api import APIError @@ -21,5 +22,9 @@ def report() -> Response: return APIError(400, "Missing values in request").as_response() reportsig = data["reportsig"] timestamp = data["timestamp"] - dbConn.insert_reportsig(reportsig, timestamp) + + try: + dbConn.insert_reportsig(reportsig, timestamp) + except DuplicateKeyError: + return APIError(403, "Entry with with this reportsig already exists").as_response() return Response(None, 200) From ce1370c57a7486d080b3ad759d98f9a42c8fa028 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 17:58:08 +0200 Subject: [PATCH 41/46] Add test for duplicates --- tests/v0/test_cases.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 641168f..00b399f 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -5,11 +5,34 @@ def test_report_post(client: FlaskClient): + default_reportsig = "teststr" + data = { - "reportsig": "teststr", + "reportsig": default_reportsig, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } res: Response = client.post( url_for(".report"), data=json.dumps(data), content_type="application/json" ) assert res.status_code == 200 + +def test_report_post_duplicate(client: FlaskClient): + duplicate_reportsig = "duplicate" + + data1 = { + "reportsig": duplicate_reportsig, + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } + res1: Response = client.post( + url_for(".report"), data=json.dumps(data1), content_type="application/json" + ) + assert res1.status_code == 200 + + data2 = { + "reportsig": duplicate_reportsig, + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } + res2: Response = client.post( + url_for(".report"), data=json.dumps(data2), content_type="application/json" + ) + assert res2.status_code == 403 From 2b3e24012d5baef90edee02faba548e55ced0fa1 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 19:42:34 +0200 Subject: [PATCH 42/46] Do nothing if the reportsig already exists After considering the consequences of returning an error (clients could check which IDs already exist), we decided to do nothing if a duplicate exists --- app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 06c3ead..a706301 100644 --- a/app.py +++ b/app.py @@ -25,6 +25,6 @@ def report() -> Response: try: dbConn.insert_reportsig(reportsig, timestamp) - except DuplicateKeyError: - return APIError(403, "Entry with with this reportsig already exists").as_response() + except: + pass return Response(None, 200) From 23ece97ba4ecfe65d0eb12e8f68541dc5c162acf Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 19:46:28 +0200 Subject: [PATCH 43/46] Apply formatting --- tests/v0/test_cases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 00b399f..8fcbd0a 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -15,7 +15,8 @@ def test_report_post(client: FlaskClient): url_for(".report"), data=json.dumps(data), content_type="application/json" ) assert res.status_code == 200 - + + def test_report_post_duplicate(client: FlaskClient): duplicate_reportsig = "duplicate" From 3adfabd59bfd31ade82b8af0d4138c6c29c2baee Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 19:46:41 +0200 Subject: [PATCH 44/46] Ignore type checking for pymongo.errors import --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index a706301..5129a3b 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,7 @@ from flask import Flask import os import json -from pymongo.errors import DuplicateKeyError +from pymongo.errors import DuplicateKeyError #type: ignore from flask import Flask, Response, request from db.db import DBConnection from models.api import APIError From 342e86ada36f9251819610f9415471157a28d4c8 Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 19:49:11 +0200 Subject: [PATCH 45/46] Apply formatting --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index 5129a3b..22728f7 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,7 @@ from flask import Flask import os import json -from pymongo.errors import DuplicateKeyError #type: ignore +from pymongo.errors import DuplicateKeyError # type: ignore from flask import Flask, Response, request from db.db import DBConnection from models.api import APIError From 4187ed2b17f12d2f5edd44599b6c189c8f24de4a Mon Sep 17 00:00:00 2001 From: calmandniceperson Date: Mon, 13 Apr 2020 19:51:58 +0200 Subject: [PATCH 46/46] Fix test for duplicate reportsigs --- tests/v0/test_cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v0/test_cases.py b/tests/v0/test_cases.py index 8fcbd0a..08935b0 100644 --- a/tests/v0/test_cases.py +++ b/tests/v0/test_cases.py @@ -36,4 +36,4 @@ def test_report_post_duplicate(client: FlaskClient): res2: Response = client.post( url_for(".report"), data=json.dumps(data2), content_type="application/json" ) - assert res2.status_code == 403 + assert res2.status_code == 200