API - use task queue to send password request email

This commit is contained in:
Sam 2020-07-14 22:03:56 +02:00
parent e990a63675
commit c797d4393e
8 changed files with 112 additions and 11 deletions

View File

@ -87,6 +87,9 @@ run-client:
run-server: run-server:
cd fittrackee_api && $(GUNICORN) -b 127.0.0.1:5000 "fittrackee_api:create_app()" --error-logfile ../gunicorn-error.log cd fittrackee_api && $(GUNICORN) -b 127.0.0.1:5000 "fittrackee_api:create_app()" --error-logfile ../gunicorn-error.log
run-workers:
$(FLASK) worker --processes=1
serve-python: serve-python:
$(FLASK) run --with-threads -h $(HOST) -p $(API_PORT) $(FLASK) run --with-threads -h $(HOST) -p $(API_PORT)

View File

@ -4,6 +4,7 @@ from importlib import import_module, reload
from flask import Flask from flask import Flask
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_dramatiq import Dramatiq
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
@ -13,6 +14,7 @@ db = SQLAlchemy()
bcrypt = Bcrypt() bcrypt = Bcrypt()
migrate = Migrate() migrate = Migrate()
email_service = Email() email_service = Email()
dramatiq = Dramatiq()
appLog = logging.getLogger('fittrackee_api') appLog = logging.getLogger('fittrackee_api')
@ -33,6 +35,7 @@ def create_app():
db.init_app(app) db.init_app(app)
bcrypt.init_app(app) bcrypt.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
dramatiq.init_app(app)
# set up email # set up email
email_service.init_email(app) email_service.init_email(app)

View File

@ -1,7 +1,15 @@
import os import os
from dramatiq.brokers.redis import RedisBroker
from dramatiq.brokers.stub import StubBroker
from flask import current_app from flask import current_app
if os.getenv('APP_SETTINGS') == 'fittrackee_api.config.Testing':
broker = StubBroker
broker.emit_after("process_boot")
else:
broker = RedisBroker
class BaseConfig: class BaseConfig:
"""Base configuration""" """Base configuration"""
@ -20,6 +28,7 @@ class BaseConfig:
UI_URL = os.environ.get('UI_URL') UI_URL = os.environ.get('UI_URL')
EMAIL_URL = os.environ.get('EMAIL_URL') EMAIL_URL = os.environ.get('EMAIL_URL')
SENDER_EMAIL = os.environ.get('SENDER_EMAIL') SENDER_EMAIL = os.environ.get('SENDER_EMAIL')
DRAMATIQ_BROKER = broker
class DevelopmentConfig(BaseConfig): class DevelopmentConfig(BaseConfig):
@ -31,6 +40,7 @@ class DevelopmentConfig(BaseConfig):
USERNAME = 'admin' USERNAME = 'admin'
PASSWORD = 'default' PASSWORD = 'default'
BCRYPT_LOG_ROUNDS = 4 BCRYPT_LOG_ROUNDS = 4
DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://')
class TestingConfig(BaseConfig): class TestingConfig(BaseConfig):

View File

@ -0,0 +1,11 @@
from fittrackee_api import dramatiq, email_service
@dramatiq.actor()
def reset_password_email(user, email_data):
email_service.send(
template='password_reset_request',
lang=user['language'],
recipient=user['email'],
data=email_data,
)

View File

@ -8,10 +8,10 @@ from fittrackee_api.application.models import AppConfig
from fittrackee_api.application.utils import update_app_config_from_database from fittrackee_api.application.utils import update_app_config_from_database
from fittrackee_api.users.models import User from fittrackee_api.users.models import User
os.environ["FLASK_ENV"] = 'testing' os.environ['FLASK_ENV'] = 'testing'
os.environ["APP_SETTINGS"] = 'fittrackee_api.config.TestingConfig' os.environ['APP_SETTINGS'] = 'fittrackee_api.config.TestingConfig'
# to avoid resetting dev database during tests # to avoid resetting dev database during tests
os.environ["DATABASE_URL"] = os.getenv("DATABASE_TEST_URL") os.environ['DATABASE_URL'] = os.getenv('DATABASE_TEST_URL')
def get_app_config(with_config=False): def get_app_config(with_config=False):

View File

@ -2,7 +2,8 @@ import datetime
import os import os
import jwt import jwt
from fittrackee_api import appLog, bcrypt, db, email_service from fittrackee_api import appLog, bcrypt, db
from fittrackee_api.tasks import reset_password_email
from flask import Blueprint, current_app, jsonify, request from flask import Blueprint, current_app, jsonify, request
from sqlalchemy import exc, or_ from sqlalchemy import exc, or_
from werkzeug.exceptions import RequestEntityTooLarge from werkzeug.exceptions import RequestEntityTooLarge
@ -712,12 +713,11 @@ def request_password_reset():
'operating_system': request.user_agent.platform, 'operating_system': request.user_agent.platform,
'browser_name': request.user_agent.browser, 'browser_name': request.user_agent.browser,
} }
email_service.send( user_data = {
template='password_reset_request', 'language': user.language if user.language else 'en',
lang=user.language if user.language else 'en', 'email': user.email,
recipient=user.email, }
data=email_data, reset_password_email.send(user_data, email_data)
)
response_object = { response_object = {
'status': 'success', 'status': 'success',
'message': 'Password reset request processed.', 'message': 'Password reset request processed.',

View File

@ -205,6 +205,29 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.16" version = "0.16"
[[package]]
category = "main"
description = "Background Processing for Python 3."
name = "dramatiq"
optional = false
python-versions = ">=3.5"
version = "1.9.0"
[package.dependencies]
prometheus-client = ">=0.2"
[package.dependencies.redis]
optional = true
version = ">=2.0,<4.0"
[package.extras]
all = ["watchdog-gevent (0.1)", "watchdog (>=0.8,<0.9)", "pylibmc (>=1.5,<2.0)", "pika (>=1.0,<2.0)", "redis (>=2.0,<4.0)"]
dev = ["watchdog-gevent (0.1)", "watchdog (>=0.8,<0.9)", "pylibmc (>=1.5,<2.0)", "pika (>=1.0,<2.0)", "redis (>=2.0,<4.0)", "alabaster", "sphinx (<1.8)", "sphinxcontrib-napoleon", "flake8", "flake8-bugbear", "flake8-quotes", "isort", "bumpversion", "hiredis", "twine", "wheel", "pytest (<4)", "pytest-benchmark", "pytest-cov", "tox"]
memcached = ["pylibmc (>=1.5,<2.0)"]
rabbitmq = ["pika (>=1.0,<2.0)"]
redis = ["redis (>=2.0,<4.0)"]
watch = ["watchdog (>=0.8,<0.9)", "watchdog-gevent (0.1)"]
[[package]] [[package]]
category = "dev" category = "dev"
description = "the modular source code checker: pep8 pyflakes and co" description = "the modular source code checker: pep8 pyflakes and co"
@ -253,6 +276,17 @@ version = "0.7.1"
Flask = "*" Flask = "*"
bcrypt = "*" bcrypt = "*"
[[package]]
category = "main"
description = "Adds Dramatiq support to your Flask application"
name = "flask-dramatiq"
optional = false
python-versions = ">=3.6,<4.0"
version = "0.6.0"
[package.dependencies]
dramatiq = ">=1.5,<2.0"
[[package]] [[package]]
category = "main" category = "main"
description = "SQLAlchemy database migrations for Flask applications using Alembic" description = "SQLAlchemy database migrations for Flask applications using Alembic"
@ -475,6 +509,17 @@ version = ">=0.12"
[package.extras] [package.extras]
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
[[package]]
category = "main"
description = "Python client for the Prometheus monitoring system."
name = "prometheus-client"
optional = false
python-versions = "*"
version = "0.8.0"
[package.extras]
twisted = ["twisted"]
[[package]] [[package]]
category = "main" category = "main"
description = "psycopg2 - Python-PostgreSQL Database Adapter" description = "psycopg2 - Python-PostgreSQL Database Adapter"
@ -708,6 +753,17 @@ commonmark = ">=0.8.1"
docutils = ">=0.11" docutils = ">=0.11"
sphinx = ">=1.3.1" sphinx = ">=1.3.1"
[[package]]
category = "main"
description = "Python client for Redis key-value store"
name = "redis"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "3.5.3"
[package.extras]
hiredis = ["hiredis (>=0.1.3)"]
[[package]] [[package]]
category = "dev" category = "dev"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
@ -997,7 +1053,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"] testing = ["jaraco.itertools", "func-timeout"]
[metadata] [metadata]
content-hash = "a9201e432af36210daf0112aafdd4bc39ba5a7299f4010bf4919c85340ab40b9" content-hash = "b8d219c99cac540afe974e9170fad8b4b0e1cd726148389edfcf29e2233cc121"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.files] [metadata.files]
@ -1163,6 +1219,10 @@ docutils = [
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
] ]
dramatiq = [
{file = "dramatiq-1.9.0-py3-none-any.whl", hash = "sha256:360cd436a434a513c87a9769943543c1d065835e3fa0b01f96c4fdd959bfa1c3"},
{file = "dramatiq-1.9.0.tar.gz", hash = "sha256:8112941ab2eda4f0288bacd137a991f9b1b1c600fe3dd5960eaba4256c873839"},
]
flake8 = [ flake8 = [
{file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
{file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
@ -1174,6 +1234,10 @@ flask = [
flask-bcrypt = [ flask-bcrypt = [
{file = "Flask-Bcrypt-0.7.1.tar.gz", hash = "sha256:d71c8585b2ee1c62024392ebdbc447438564e2c8c02b4e57b56a4cafd8d13c5f"}, {file = "Flask-Bcrypt-0.7.1.tar.gz", hash = "sha256:d71c8585b2ee1c62024392ebdbc447438564e2c8c02b4e57b56a4cafd8d13c5f"},
] ]
flask-dramatiq = [
{file = "flask-dramatiq-0.6.0.tar.gz", hash = "sha256:63709e73d7c8d2e5d9bc554d1e859d91c5c5c9a4ebc9461752655bf1e0b87420"},
{file = "flask_dramatiq-0.6.0-py3-none-any.whl", hash = "sha256:7d4a9289721577f726183f7c44c6713a16bbdff54b946f27abc2ffcc65768adf"},
]
flask-migrate = [ flask-migrate = [
{file = "Flask-Migrate-2.5.3.tar.gz", hash = "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"}, {file = "Flask-Migrate-2.5.3.tar.gz", hash = "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"},
{file = "Flask_Migrate-2.5.3-py2.py3-none-any.whl", hash = "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732"}, {file = "Flask_Migrate-2.5.3-py2.py3-none-any.whl", hash = "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732"},
@ -1308,6 +1372,10 @@ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
] ]
prometheus-client = [
{file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
{file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
]
psycopg2-binary = [ psycopg2-binary = [
{file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"}, {file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"}, {file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"},
@ -1417,6 +1485,10 @@ recommonmark = [
{file = "recommonmark-0.6.0-py2.py3-none-any.whl", hash = "sha256:2ec4207a574289355d5b6ae4ae4abb29043346ca12cdd5f07d374dc5987d2852"}, {file = "recommonmark-0.6.0-py2.py3-none-any.whl", hash = "sha256:2ec4207a574289355d5b6ae4ae4abb29043346ca12cdd5f07d374dc5987d2852"},
{file = "recommonmark-0.6.0.tar.gz", hash = "sha256:29cd4faeb6c5268c633634f2d69aef9431e0f4d347f90659fd0aab20e541efeb"}, {file = "recommonmark-0.6.0.tar.gz", hash = "sha256:29cd4faeb6c5268c633634f2d69aef9431e0f4d347f90659fd0aab20e541efeb"},
] ]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
{file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
]
regex = [ regex = [
{file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"},
{file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"},

View File

@ -19,6 +19,8 @@ python-forecastio = "^1.4"
gunicorn = "^20.0" gunicorn = "^20.0"
tqdm = "^4.42" tqdm = "^4.42"
humanize = "^2.5.0" humanize = "^2.5.0"
dramatiq = {extras = ["redis"], version = "^1.9.0"}
flask-dramatiq = "^0.6.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = "^19.10b0" black = "^19.10b0"