2022-10-12 10:22:22 -04:00
|
|
|
"""__init__."""
|
|
|
|
import os
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
import connexion # type: ignore
|
|
|
|
import flask.app
|
|
|
|
import flask.json
|
|
|
|
import sqlalchemy
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler # type: ignore
|
2022-10-25 16:55:11 -04:00
|
|
|
from apscheduler.schedulers.base import BaseScheduler # type: ignore
|
2022-10-12 10:22:22 -04:00
|
|
|
from flask.json.provider import DefaultJSONProvider
|
2023-01-17 12:56:06 -05:00
|
|
|
from spiffworkflow_backend.exceptions.api_error import api_error_blueprint
|
|
|
|
from spiffworkflow_backend.models.db import db
|
|
|
|
from spiffworkflow_backend.models.db import migrate
|
2022-10-12 10:22:22 -04:00
|
|
|
from flask_cors import CORS # type: ignore
|
|
|
|
from flask_mail import Mail # type: ignore
|
2022-11-18 16:45:44 -05:00
|
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
|
|
|
|
import spiffworkflow_backend.load_database_models # noqa: F401
|
2022-10-12 10:22:22 -04:00
|
|
|
from spiffworkflow_backend.config import setup_config
|
2022-12-21 12:13:33 -05:00
|
|
|
from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX
|
2022-10-12 10:22:22 -04:00
|
|
|
from spiffworkflow_backend.routes.admin_blueprint.admin_blueprint import admin_blueprint
|
2022-12-01 14:12:25 -05:00
|
|
|
from spiffworkflow_backend.routes.openid_blueprint.openid_blueprint import (
|
|
|
|
openid_blueprint,
|
|
|
|
)
|
2023-01-12 08:12:16 -05:00
|
|
|
from spiffworkflow_backend.routes.user import set_new_access_token_in_cookie
|
|
|
|
from spiffworkflow_backend.routes.user import verify_token
|
2022-10-12 10:22:22 -04:00
|
|
|
from spiffworkflow_backend.routes.user_blueprint import user_blueprint
|
2022-10-20 16:00:12 -04:00
|
|
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
2022-10-12 10:22:22 -04:00
|
|
|
from spiffworkflow_backend.services.background_processing_service import (
|
|
|
|
BackgroundProcessingService,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class MyJSONEncoder(DefaultJSONProvider):
|
|
|
|
"""MyJSONEncoder."""
|
|
|
|
|
|
|
|
def default(self, obj: Any) -> Any:
|
|
|
|
"""Default."""
|
|
|
|
if hasattr(obj, "serialized"):
|
|
|
|
return obj.serialized
|
|
|
|
elif isinstance(obj, sqlalchemy.engine.row.Row): # type: ignore
|
|
|
|
return_dict = {}
|
|
|
|
for row_key in obj.keys():
|
|
|
|
row_value = obj[row_key]
|
2022-11-11 16:31:48 -05:00
|
|
|
if hasattr(row_value, "serialized"):
|
|
|
|
return_dict.update(row_value.serialized)
|
|
|
|
elif hasattr(row_value, "__dict__"):
|
2022-10-12 10:22:22 -04:00
|
|
|
return_dict.update(row_value.__dict__)
|
|
|
|
else:
|
|
|
|
return_dict.update({row_key: row_value})
|
2022-11-11 16:31:48 -05:00
|
|
|
if "_sa_instance_state" in return_dict:
|
|
|
|
return_dict.pop("_sa_instance_state")
|
2022-10-12 10:22:22 -04:00
|
|
|
return return_dict
|
|
|
|
return super().default(obj)
|
|
|
|
|
|
|
|
def dumps(self, obj: Any, **kwargs: Any) -> Any:
|
|
|
|
"""Dumps."""
|
|
|
|
kwargs.setdefault("default", self.default)
|
|
|
|
return super().dumps(obj, **kwargs)
|
|
|
|
|
|
|
|
|
2022-10-25 16:55:11 -04:00
|
|
|
def start_scheduler(
|
|
|
|
app: flask.app.Flask, scheduler_class: BaseScheduler = BackgroundScheduler
|
|
|
|
) -> None:
|
2022-10-12 10:22:22 -04:00
|
|
|
"""Start_scheduler."""
|
2022-10-25 16:55:11 -04:00
|
|
|
scheduler = scheduler_class()
|
2022-10-12 10:22:22 -04:00
|
|
|
scheduler.add_job(
|
|
|
|
BackgroundProcessingService(app).process_message_instances_with_app_context,
|
|
|
|
"interval",
|
|
|
|
seconds=10,
|
|
|
|
)
|
|
|
|
scheduler.add_job(
|
2022-11-21 14:12:04 -05:00
|
|
|
BackgroundProcessingService(app).process_waiting_process_instances,
|
2022-10-12 10:22:22 -04:00
|
|
|
"interval",
|
2022-11-21 14:12:04 -05:00
|
|
|
seconds=10,
|
2022-10-12 10:22:22 -04:00
|
|
|
)
|
|
|
|
scheduler.start()
|
|
|
|
|
|
|
|
|
|
|
|
def create_app() -> flask.app.Flask:
|
|
|
|
"""Create_app."""
|
|
|
|
# We need to create the sqlite database in a known location.
|
|
|
|
# If we rely on the app.instance_path without setting an environment
|
|
|
|
# variable, it will be one thing when we run flask db upgrade in the
|
|
|
|
# noxfile and another thing when the tests actually run.
|
|
|
|
# instance_path is described more at https://flask.palletsprojects.com/en/2.1.x/config/
|
|
|
|
connexion_app = connexion.FlaskApp(
|
|
|
|
__name__, server_args={"instance_path": os.environ.get("FLASK_INSTANCE_PATH")}
|
|
|
|
)
|
|
|
|
app = connexion_app.app
|
|
|
|
app.config["CONNEXION_APP"] = connexion_app
|
|
|
|
app.config["SESSION_TYPE"] = "filesystem"
|
|
|
|
|
|
|
|
if os.environ.get("FLASK_SESSION_SECRET_KEY") is None:
|
|
|
|
raise KeyError(
|
2022-12-30 23:08:00 -05:00
|
|
|
"Cannot find the secret_key from the environment. Please set"
|
|
|
|
" FLASK_SESSION_SECRET_KEY"
|
2022-10-12 10:22:22 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
app.secret_key = os.environ.get("FLASK_SESSION_SECRET_KEY")
|
|
|
|
|
|
|
|
setup_config(app)
|
|
|
|
db.init_app(app)
|
|
|
|
migrate.init_app(app, db)
|
|
|
|
|
|
|
|
app.register_blueprint(user_blueprint)
|
|
|
|
app.register_blueprint(api_error_blueprint)
|
|
|
|
app.register_blueprint(admin_blueprint, url_prefix="/admin")
|
2022-11-30 11:32:55 -05:00
|
|
|
app.register_blueprint(openid_blueprint, url_prefix="/openid")
|
2022-10-12 10:22:22 -04:00
|
|
|
|
2022-12-06 08:24:02 -05:00
|
|
|
# preflight options requests will be allowed if they meet the requirements of the url regex.
|
|
|
|
# we will add an Access-Control-Max-Age header to the response to tell the browser it doesn't
|
|
|
|
# need to continually keep asking for the same path.
|
2022-10-12 10:22:22 -04:00
|
|
|
origins_re = [
|
|
|
|
r"^https?:\/\/%s(.*)" % o.replace(".", r"\.")
|
|
|
|
for o in app.config["CORS_ALLOW_ORIGINS"]
|
|
|
|
]
|
2023-01-11 15:57:01 -05:00
|
|
|
CORS(app, origins=origins_re, max_age=3600, supports_credentials=True)
|
2022-10-12 10:22:22 -04:00
|
|
|
|
2022-12-21 12:13:33 -05:00
|
|
|
connexion_app.add_api("api.yml", base_path=V1_API_PATH_PREFIX)
|
2022-10-12 10:22:22 -04:00
|
|
|
|
|
|
|
mail = Mail(app)
|
|
|
|
app.config["MAIL_APP"] = mail
|
|
|
|
|
|
|
|
app.json = MyJSONEncoder(app)
|
|
|
|
|
2022-10-25 16:55:11 -04:00
|
|
|
if app.config["RUN_BACKGROUND_SCHEDULER"]:
|
2022-10-12 10:22:22 -04:00
|
|
|
start_scheduler(app)
|
|
|
|
|
|
|
|
configure_sentry(app)
|
|
|
|
|
2022-10-20 16:00:12 -04:00
|
|
|
app.before_request(verify_token)
|
|
|
|
app.before_request(AuthorizationService.check_for_permission)
|
2023-01-11 17:27:12 -05:00
|
|
|
app.after_request(set_new_access_token_in_cookie)
|
2022-10-20 16:00:12 -04:00
|
|
|
|
2022-10-12 10:22:22 -04:00
|
|
|
return app # type: ignore
|
|
|
|
|
|
|
|
|
2022-10-12 15:28:52 -04:00
|
|
|
def get_hacked_up_app_for_script() -> flask.app.Flask:
|
|
|
|
"""Get_hacked_up_app_for_script."""
|
|
|
|
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "development"
|
|
|
|
flask_env_key = "FLASK_SESSION_SECRET_KEY"
|
|
|
|
os.environ[flask_env_key] = "whatevs"
|
|
|
|
if "BPMN_SPEC_ABSOLUTE_DIR" not in os.environ:
|
|
|
|
home = os.environ["HOME"]
|
|
|
|
full_process_model_path = (
|
|
|
|
f"{home}/projects/github/sartography/sample-process-models"
|
|
|
|
)
|
|
|
|
if os.path.isdir(full_process_model_path):
|
|
|
|
os.environ["BPMN_SPEC_ABSOLUTE_DIR"] = full_process_model_path
|
|
|
|
else:
|
|
|
|
raise Exception(f"Could not find {full_process_model_path}")
|
|
|
|
app = create_app()
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
2022-10-12 10:22:22 -04:00
|
|
|
def configure_sentry(app: flask.app.Flask) -> None:
|
|
|
|
"""Configure_sentry."""
|
|
|
|
import sentry_sdk
|
|
|
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
|
|
|
|
2022-10-27 15:33:59 -04:00
|
|
|
# get rid of NotFound errors
|
2022-10-18 07:09:34 -04:00
|
|
|
def before_send(event: Any, hint: Any) -> Any:
|
|
|
|
"""Before_send."""
|
|
|
|
if "exc_info" in hint:
|
|
|
|
_exc_type, exc_value, _tb = hint["exc_info"]
|
|
|
|
# NotFound is mostly from web crawlers
|
|
|
|
if isinstance(exc_value, NotFound):
|
|
|
|
return None
|
|
|
|
return event
|
|
|
|
|
2022-10-27 10:50:50 -04:00
|
|
|
sentry_errors_sample_rate = app.config.get("SENTRY_ERRORS_SAMPLE_RATE")
|
|
|
|
if sentry_errors_sample_rate is None:
|
|
|
|
raise Exception("SENTRY_ERRORS_SAMPLE_RATE is not set somehow")
|
|
|
|
|
|
|
|
sentry_traces_sample_rate = app.config.get("SENTRY_TRACES_SAMPLE_RATE")
|
|
|
|
if sentry_traces_sample_rate is None:
|
|
|
|
raise Exception("SENTRY_TRACES_SAMPLE_RATE is not set somehow")
|
|
|
|
|
2022-10-12 10:22:22 -04:00
|
|
|
sentry_sdk.init(
|
|
|
|
dsn=app.config.get("SENTRY_DSN"),
|
|
|
|
integrations=[
|
|
|
|
FlaskIntegration(),
|
|
|
|
],
|
2022-10-18 07:09:34 -04:00
|
|
|
environment=app.config["ENV_IDENTIFIER"],
|
2022-10-27 10:50:50 -04:00
|
|
|
# sample_rate is the errors sample rate. we usually set it to 1 (100%)
|
|
|
|
# so we get all errors in sentry.
|
|
|
|
sample_rate=float(sentry_errors_sample_rate),
|
|
|
|
# Set traces_sample_rate to capture a certain percentage
|
2022-10-12 10:22:22 -04:00
|
|
|
# of transactions for performance monitoring.
|
2022-10-27 10:50:50 -04:00
|
|
|
# We recommend adjusting this value to less than 1(00%) in production.
|
|
|
|
traces_sample_rate=float(sentry_traces_sample_rate),
|
2022-10-18 07:09:34 -04:00
|
|
|
before_send=before_send,
|
2022-10-12 10:22:22 -04:00
|
|
|
)
|