backend/__init__.py (677 lines of code) (raw):
import logging
import os
from logging.handlers import RotatingFileHandler
from flask import Flask, redirect
from flask_cors import CORS
from flask_migrate import Migrate
from flask_oauthlib.client import OAuth
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
from backend.config import EnvironmentConfig
def sentry_init():
"""Initialize sentry.io event tracking"""
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn=EnvironmentConfig.SENTRY_BACKEND_DSN,
environment=EnvironmentConfig.ENVIRONMENT,
integrations=[FlaskIntegration()],
traces_sample_rate=0.1,
)
def format_url(endpoint):
parts = endpoint.strip("/")
return "/api/{}/{}/".format(EnvironmentConfig.API_VERSION, parts)
db = SQLAlchemy()
migrate = Migrate()
oauth = OAuth()
osm = oauth.remote_app("osm", app_key="OSM_OAUTH_SETTINGS")
# Import all models so that they are registered with SQLAlchemy
from backend.models.postgis import * # noqa
def create_app(env="backend.config.EnvironmentConfig"):
"""
Bootstrap function to initialise the Flask app and config
:return: Initialised Flask app
"""
# If SENTRY_BACKEND_DSN is configured, init sentry_sdk tracking
if EnvironmentConfig.SENTRY_BACKEND_DSN:
sentry_init()
app = Flask(__name__, template_folder="services/messaging/templates/")
# Load configuration options from environment
app.config.from_object(env)
# Enable logging to files
initialise_logger(app)
app.logger.info("Starting up a new Tasking Manager application")
# Connect to database
app.logger.debug("Connecting to the database")
db.init_app(app)
migrate.init_app(app, db)
app.logger.debug("Add root redirect route")
@app.route("/")
def index_redirect():
return redirect(format_url("system/heartbeat/"), code=302)
# Add paths to API endpoints
add_api_endpoints(app)
# Enables CORS on all API routes, meaning API is callable from anywhere
CORS(app)
# Add basic oauth setup
app.secret_key = app.config[
"SECRET_KEY"
] # Required by itsdangerous, Flask-OAuthlib for creating entropy
oauth.init_app(app)
return app
def initialise_logger(app):
"""
Read environment config then initialise a 2MB rotating log. Prod Log Level can be reduced to help diagnose Prod
only issues.
"""
log_dir = app.config["LOG_DIR"]
log_level = app.config["LOG_LEVEL"]
if not os.path.exists(log_dir):
os.makedirs(log_dir)
file_handler = RotatingFileHandler(
log_dir + "/tasking-manager.log", "a", 2 * 1024 * 1024, 3
)
file_handler.setLevel(log_level)
file_handler.setFormatter(
logging.Formatter(
"%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]"
)
)
app.logger.addHandler(file_handler)
app.logger.setLevel(log_level)
def initialise_counters(app):
""" Initialise homepage counters so that users don't see 0 users on first load of application"""
from backend.services.stats_service import StatsService
with app.app_context():
StatsService.get_homepage_stats()
def add_api_endpoints(app):
"""
Define the routes the API exposes using Flask-Restful.
"""
app.logger.debug("Adding routes to API endpoints")
api = Api(app)
# Projects API import
from backend.api.projects.resources import (
ProjectsRestAPI,
ProjectsAllAPI,
ProjectsQueriesBboxAPI,
ProjectsQueriesOwnerAPI,
ProjectsQueriesTouchedAPI,
ProjectsQueriesSummaryAPI,
ProjectsQueriesNoGeometriesAPI,
ProjectsQueriesNoTasksAPI,
ProjectsQueriesAoiAPI,
ProjectsQueriesPriorityAreasAPI,
ProjectsQueriesFeaturedAPI,
)
from backend.api.projects.activities import (
ProjectsActivitiesAPI,
ProjectsLastActivitiesAPI,
)
from backend.api.projects.contributions import (
ProjectsContributionsAPI,
ProjectsContributionsQueriesDayAPI,
)
from backend.api.projects.statistics import (
ProjectsStatisticsAPI,
ProjectsStatisticsQueriesUsernameAPI,
ProjectsStatisticsQueriesPopularAPI,
)
from backend.api.projects.teams import ProjectsTeamsAPI
from backend.api.projects.campaigns import ProjectsCampaignsAPI
from backend.api.projects.actions import (
ProjectsActionsTransferAPI,
ProjectsActionsMessageContributorsAPI,
ProjectsActionsFeatureAPI,
ProjectsActionsUnFeatureAPI,
ProjectsActionsSetInterestsAPI,
ProjectActionsIntersectingTilesAPI,
)
from backend.api.projects.favorites import ProjectsFavoritesAPI
# Tasks API import
from backend.api.tasks.resources import (
TasksRestAPI,
TasksQueriesJsonAPI,
TasksQueriesXmlAPI,
TasksQueriesGpxAPI,
TasksQueriesAoiAPI,
TasksQueriesMappedAPI,
TasksQueriesOwnInvalidatedAPI,
)
from backend.api.tasks.actions import (
TasksActionsMappingLockAPI,
TasksActionsMappingStopAPI,
TasksActionsMappingUnlockAPI,
TasksActionsMappingUndoAPI,
TasksActionsValidationLockAPI,
TasksActionsValidationStopAPI,
TasksActionsValidationUnlockAPI,
TasksActionsMapAllAPI,
TasksActionsValidateAllAPI,
TasksActionsInvalidateAllAPI,
TasksActionsResetBadImageryAllAPI,
TasksActionsResetAllAPI,
TasksActionsSplitAPI,
)
from backend.api.tasks.statistics import (
TasksStatisticsAPI,
)
# Comments API impor
from backend.api.comments.resources import (
CommentsProjectsRestAPI,
CommentsTasksRestAPI,
)
# Annotations API import
from backend.api.annotations.resources import AnnotationsRestAPI
# Issues API import
from backend.api.issues.resources import IssuesRestAPI, IssuesAllAPI
# Interests API import
from backend.api.interests.resources import InterestsRestAPI, InterestsAllAPI
# Licenses API import
from backend.api.licenses.resources import LicensesRestAPI, LicensesAllAPI
from backend.api.licenses.actions import LicensesActionsAcceptAPI
# Campaigns API endpoint
from backend.api.campaigns.resources import CampaignsRestAPI, CampaignsAllAPI
# Organisations API endpoint
from backend.api.organisations.resources import (
OrganisationsStatsAPI,
OrganisationsRestAPI,
OrganisationsBySlugRestAPI,
OrganisationsAllAPI,
)
from backend.api.organisations.campaigns import OrganisationsCampaignsAPI
# Countries API endpoint
from backend.api.countries.resources import CountriesRestAPI
# Teams API endpoint
from backend.api.teams.resources import TeamsRestAPI, TeamsAllAPI
from backend.api.teams.actions import (
TeamsActionsJoinAPI,
TeamsActionsLeaveAPI,
TeamsActionsMessageMembersAPI,
)
# Notifications API endpoint
from backend.api.notifications.resources import (
NotificationsRestAPI,
NotificationsAllAPI,
NotificationsQueriesCountUnreadAPI,
NotificationsQueriesPostUnreadAPI,
)
from backend.api.notifications.actions import NotificationsActionsDeleteMultipleAPI
# Users API endpoint
from backend.api.users.resources import (
UsersRestAPI,
UsersAllAPI,
UsersQueriesUsernameAPI,
UsersQueriesUsernameFilterAPI,
UsersQueriesOwnLockedAPI,
UsersQueriesOwnLockedDetailsAPI,
UsersQueriesFavoritesAPI,
UsersQueriesInterestsAPI,
UsersRecommendedProjectsAPI,
)
from backend.api.users.tasks import UsersTasksAPI
from backend.api.users.actions import (
UsersActionsSetUsersAPI,
UsersActionsSetLevelAPI,
UsersActionsSetRoleAPI,
UsersActionsSetExpertModeAPI,
UsersActionsVerifyEmailAPI,
UsersActionsRegisterEmailAPI,
UsersActionsSetInterestsAPI,
)
from backend.api.users.openstreetmap import UsersOpenStreetMapAPI
from backend.api.users.statistics import (
UsersStatisticsAPI,
UsersStatisticsInterestsAPI,
UsersStatisticsAllAPI,
)
# System API endpoint
from backend.api.system.general import (
SystemDocsAPI,
SystemHeartbeatAPI,
SystemLanguagesAPI,
SystemContactAdminRestAPI,
)
from backend.api.system.statistics import SystemStatisticsAPI
from backend.api.system.authentication import (
SystemAuthenticationEmailAPI,
SystemAuthenticationLoginAPI,
SystemAuthenticationCallbackAPI,
)
from backend.api.system.applications import SystemApplicationsRestAPI
from backend.api.system.image_upload import SystemImageUploadRestAPI
# Projects REST endpoint
api.add_resource(ProjectsAllAPI, format_url("projects/"), methods=["GET"])
api.add_resource(
ProjectsRestAPI,
format_url("projects/"),
endpoint="create_project",
methods=["POST"],
)
api.add_resource(
ProjectsRestAPI,
format_url("projects/<int:project_id>/"),
methods=["GET", "PATCH", "DELETE"],
)
# Projects queries endoints (TODO: Refactor them into the REST endpoints)
api.add_resource(ProjectsQueriesBboxAPI, format_url("projects/queries/bbox/"))
api.add_resource(
ProjectsQueriesOwnerAPI, format_url("projects/queries/myself/owner/")
)
api.add_resource(
ProjectsQueriesTouchedAPI,
format_url("projects/queries/<string:username>/touched/"),
)
api.add_resource(
ProjectsQueriesSummaryAPI,
format_url("projects/<int:project_id>/queries/summary/"),
)
api.add_resource(
ProjectsQueriesNoGeometriesAPI,
format_url("projects/<int:project_id>/queries/nogeometries/"),
)
api.add_resource(
ProjectsQueriesNoTasksAPI,
format_url("projects/<int:project_id>/queries/notasks/"),
)
api.add_resource(
ProjectsQueriesAoiAPI, format_url("projects/<int:project_id>/queries/aoi/")
)
api.add_resource(
ProjectsQueriesPriorityAreasAPI,
format_url("projects/<int:project_id>/queries/priority-areas/"),
)
api.add_resource(
ProjectsQueriesFeaturedAPI, format_url("projects/queries/featured/")
)
# Projects' addtional resources
api.add_resource(
ProjectsActivitiesAPI, format_url("projects/<int:project_id>/activities/")
)
api.add_resource(
ProjectsLastActivitiesAPI,
format_url("projects/<int:project_id>/activities/latest/"),
)
api.add_resource(
ProjectsContributionsAPI, format_url("projects/<int:project_id>/contributions/")
)
api.add_resource(
ProjectsContributionsQueriesDayAPI,
format_url("projects/<int:project_id>/contributions/queries/day/"),
)
api.add_resource(
ProjectsStatisticsAPI, format_url("projects/<int:project_id>/statistics/")
)
api.add_resource(
ProjectsStatisticsQueriesUsernameAPI,
format_url("projects/<int:project_id>/statistics/queries/<string:username>/"),
)
api.add_resource(
ProjectsStatisticsQueriesPopularAPI, format_url("projects/queries/popular/")
)
api.add_resource(
ProjectsTeamsAPI,
format_url("projects/<int:project_id>/teams/"),
endpoint="get_all_project_teams",
methods=["GET"],
)
api.add_resource(
ProjectsTeamsAPI,
format_url("projects/<int:project_id>/teams/<int:team_id>/"),
methods=["POST", "DELETE", "PATCH"],
)
api.add_resource(
ProjectsCampaignsAPI,
format_url("projects/<int:project_id>/campaigns/"),
endpoint="get_all_project_campaigns",
methods=["GET"],
)
api.add_resource(
ProjectsCampaignsAPI,
format_url("projects/<int:project_id>/campaigns/<int:campaign_id>/"),
endpoint="assign_remove_campaign_to_project",
methods=["POST", "DELETE"],
)
# Projects actions endoints
api.add_resource(
ProjectsActionsMessageContributorsAPI,
format_url("projects/<int:project_id>/actions/message-contributors/"),
)
api.add_resource(
ProjectsActionsTransferAPI,
format_url("projects/<int:project_id>/actions/transfer-ownership/"),
)
api.add_resource(
ProjectsActionsFeatureAPI,
format_url("projects/<int:project_id>/actions/feature/"),
)
api.add_resource(
ProjectsActionsUnFeatureAPI,
format_url("projects/<int:project_id>/actions/remove-feature/"),
methods=["POST"],
)
api.add_resource(
ProjectsFavoritesAPI,
format_url("projects/<int:project_id>/favorite/"),
methods=["GET", "POST", "DELETE"],
)
api.add_resource(
ProjectsActionsSetInterestsAPI,
format_url("projects/<int:project_id>/actions/set-interests/"),
methods=["POST"],
)
api.add_resource(
ProjectActionsIntersectingTilesAPI,
format_url("projects/actions/intersecting-tiles/"),
methods=["POST"],
)
api.add_resource(
UsersActionsSetInterestsAPI,
format_url("users/me/actions/set-interests/"),
endpoint="create_user_interest",
methods=["POST"],
)
api.add_resource(
UsersStatisticsInterestsAPI,
format_url("users/<int:user_id>/statistics/interests/"),
methods=["GET"],
)
api.add_resource(
InterestsAllAPI,
format_url("interests/"),
endpoint="create_interest",
methods=["POST", "GET"],
)
api.add_resource(
InterestsRestAPI,
format_url("interests/<int:interest_id>/"),
methods=["GET", "PATCH", "DELETE"],
)
# Tasks REST endpoint
api.add_resource(
TasksRestAPI, format_url("projects/<int:project_id>/tasks/<int:task_id>/")
)
# Tasks queries endoints (TODO: Refactor them into the REST endpoints)
api.add_resource(
TasksQueriesJsonAPI,
format_url("projects/<int:project_id>/tasks/"),
methods=["GET", "DELETE"],
)
api.add_resource(
TasksQueriesXmlAPI, format_url("projects/<int:project_id>/tasks/queries/xml/")
)
api.add_resource(
TasksQueriesGpxAPI, format_url("projects/<int:project_id>/tasks/queries/gpx/")
)
api.add_resource(
TasksQueriesAoiAPI, format_url("projects/<int:project_id>/tasks/queries/aoi/")
)
api.add_resource(
TasksQueriesMappedAPI,
format_url("projects/<int:project_id>/tasks/queries/mapped/"),
)
api.add_resource(
TasksQueriesOwnInvalidatedAPI,
format_url("projects/<int:project_id>/tasks/queries/own/invalidated/"),
)
# Tasks actions endoints
api.add_resource(
TasksActionsMappingLockAPI,
format_url(
"projects/<int:project_id>/tasks/actions/lock-for-mapping/<int:task_id>/"
),
)
api.add_resource(
TasksActionsMappingStopAPI,
format_url(
"projects/<int:project_id>/tasks/actions/stop-mapping/<int:task_id>/"
),
)
api.add_resource(
TasksActionsMappingUnlockAPI,
format_url(
"projects/<int:project_id>/tasks/actions/unlock-after-mapping/<int:task_id>/"
),
)
api.add_resource(
TasksActionsMappingUndoAPI,
format_url(
"projects/<int:project_id>/tasks/actions/undo-last-action/<int:task_id>/"
),
)
api.add_resource(
TasksActionsValidationLockAPI,
format_url("projects/<int:project_id>/tasks/actions/lock-for-validation/"),
)
api.add_resource(
TasksActionsValidationStopAPI,
format_url("projects/<int:project_id>/tasks/actions/stop-validation/"),
)
api.add_resource(
TasksActionsValidationUnlockAPI,
format_url("projects/<int:project_id>/tasks/actions/unlock-after-validation/"),
)
api.add_resource(
TasksActionsMapAllAPI,
format_url("projects/<int:project_id>/tasks/actions/map-all/"),
)
api.add_resource(
TasksActionsValidateAllAPI,
format_url("projects/<int:project_id>/tasks/actions/validate-all/"),
)
api.add_resource(
TasksActionsInvalidateAllAPI,
format_url("projects/<int:project_id>/tasks/actions/invalidate-all/"),
)
api.add_resource(
TasksActionsResetBadImageryAllAPI,
format_url("projects/<int:project_id>/tasks/actions/reset-all-badimagery/"),
)
api.add_resource(
TasksActionsResetAllAPI,
format_url("projects/<int:project_id>/tasks/actions/reset-all/"),
)
api.add_resource(
TasksActionsSplitAPI,
format_url("projects/<int:project_id>/tasks/actions/split/<int:task_id>/"),
)
# Tasks Statistics endpoint
api.add_resource(
TasksStatisticsAPI,
format_url("tasks/statistics/"),
methods=["GET"],
)
# Comments REST endoints
api.add_resource(
CommentsProjectsRestAPI,
format_url("projects/<int:project_id>/comments/"),
methods=["GET", "POST"],
)
api.add_resource(
CommentsTasksRestAPI,
format_url("projects/<int:project_id>/comments/tasks/<int:task_id>/"),
methods=["GET", "POST"],
)
# Annotations REST endoints
api.add_resource(
AnnotationsRestAPI,
format_url("projects/<int:project_id>/annotations/<string:annotation_type>/"),
format_url("projects/<int:project_id>/annotations/"),
methods=["GET", "POST"],
)
# Issues REST endpoints
api.add_resource(
IssuesAllAPI, format_url("tasks/issues/categories/"), methods=["GET", "POST"]
)
api.add_resource(
IssuesRestAPI,
format_url("tasks/issues/categories/<int:category_id>/"),
methods=["GET", "PATCH", "DELETE"],
)
# Licenses REST endpoints
api.add_resource(LicensesAllAPI, format_url("licenses/"))
api.add_resource(
LicensesRestAPI,
format_url("licenses/"),
endpoint="create_license",
methods=["POST"],
)
api.add_resource(
LicensesRestAPI,
format_url("licenses/<int:license_id>/"),
methods=["GET", "PATCH", "DELETE"],
)
# Licenses actions endpoint
api.add_resource(
LicensesActionsAcceptAPI,
format_url("licenses/<int:license_id>/actions/accept-for-me/"),
)
# Countries REST endpoints
api.add_resource(CountriesRestAPI, format_url("countries/"))
# Organisations REST endpoints
api.add_resource(OrganisationsAllAPI, format_url("organisations/"))
api.add_resource(
OrganisationsRestAPI,
format_url("organisations/"),
endpoint="create_organisation",
methods=["POST"],
)
api.add_resource(
OrganisationsRestAPI,
format_url("organisations/<int:organisation_id>/"),
endpoint="get_organisation",
methods=["GET"],
)
api.add_resource(
OrganisationsBySlugRestAPI,
format_url("organisations/<string:slug>/"),
endpoint="get_organisation_by_slug",
methods=["GET"],
)
api.add_resource(
OrganisationsRestAPI,
format_url("organisations/<int:organisation_id>/"),
methods=["PUT", "DELETE", "PATCH"],
)
# Organisations additional resources endpoints
api.add_resource(
OrganisationsStatsAPI,
format_url("organisations/<int:organisation_id>/statistics/"),
endpoint="get_organisation_stats",
methods=["GET"],
)
api.add_resource(
OrganisationsCampaignsAPI,
format_url("organisations/<int:organisation_id>/campaigns/"),
endpoint="get_all_organisation_campaigns",
methods=["GET"],
)
api.add_resource(
OrganisationsCampaignsAPI,
format_url("organisations/<int:organisation_id>/campaigns/<int:campaign_id>/"),
endpoint="assign_campaign_to_organisation",
methods=["POST", "DELETE"],
)
# Teams REST endpoints
api.add_resource(TeamsAllAPI, format_url("teams"), methods=["GET"])
api.add_resource(
TeamsAllAPI, format_url("teams/"), endpoint="create_team", methods=["POST"]
)
api.add_resource(
TeamsRestAPI,
format_url("teams/<int:team_id>/"),
methods=["GET", "PUT", "DELETE", "PATCH"],
)
# Teams actions endpoints
api.add_resource(
TeamsActionsJoinAPI,
format_url("teams/<int:team_id>/actions/join/"),
methods=["POST", "PATCH"],
)
api.add_resource(
TeamsActionsLeaveAPI,
format_url("teams/<int:team_id>/actions/leave/"),
endpoint="leave_team",
methods=["POST"],
)
api.add_resource(
TeamsActionsMessageMembersAPI,
format_url("teams/<int:team_id>/actions/message-members/"),
)
# Campaigns REST endpoints
api.add_resource(
CampaignsAllAPI,
format_url("campaigns/"),
endpoint="get_all_campaign",
methods=["GET"],
)
api.add_resource(
CampaignsAllAPI,
format_url("campaigns/"),
endpoint="create_campaign",
methods=["POST"],
)
api.add_resource(
CampaignsRestAPI,
format_url("campaigns/<int:campaign_id>/"),
methods=["GET", "PATCH", "DELETE"],
)
# Notifications REST endpoints
api.add_resource(
NotificationsRestAPI, format_url("notifications/<int:message_id>/")
)
api.add_resource(NotificationsAllAPI, format_url("notifications/"))
api.add_resource(
NotificationsQueriesCountUnreadAPI,
format_url("notifications/queries/own/count-unread/"),
)
api.add_resource(
NotificationsQueriesPostUnreadAPI,
format_url("notifications/queries/own/post-unread/"),
methods=["POST"],
)
# Notifications Actions endpoints
api.add_resource(
NotificationsActionsDeleteMultipleAPI,
format_url("notifications/delete-multiple/"),
methods=["DELETE"],
)
# Users REST endpoint
api.add_resource(UsersAllAPI, format_url("users/"))
api.add_resource(UsersRestAPI, format_url("users/<int:user_id>/"))
api.add_resource(
UsersQueriesUsernameFilterAPI,
format_url("users/queries/filter/<string:username>/"),
)
api.add_resource(
UsersQueriesUsernameAPI, format_url("users/queries/<string:username>/")
)
api.add_resource(UsersQueriesFavoritesAPI, format_url("users/queries/favorites/"))
api.add_resource(
UsersQueriesOwnLockedAPI, format_url("users/queries/tasks/locked/")
)
api.add_resource(
UsersQueriesOwnLockedDetailsAPI,
format_url("users/queries/tasks/locked/details/"),
)
# Users Actions endpoint
api.add_resource(UsersActionsSetUsersAPI, format_url("users/me/actions/set-user/"))
api.add_resource(
UsersActionsSetLevelAPI,
format_url("users/<string:username>/actions/set-level/<string:level>/"),
)
api.add_resource(
UsersActionsSetRoleAPI,
format_url("users/<string:username>/actions/set-role/<string:role>/"),
)
api.add_resource(
UsersActionsSetExpertModeAPI,
format_url(
"users/<string:username>/actions/set-expert-mode/<string:is_expert>/"
),
)
api.add_resource(UsersTasksAPI, format_url("users/<int:user_id>/tasks/"))
api.add_resource(
UsersActionsVerifyEmailAPI, format_url("users/me/actions/verify-email/")
)
api.add_resource(
UsersActionsRegisterEmailAPI, format_url("users/actions/register/")
)
# Users Statistics endpoint
api.add_resource(
UsersStatisticsAPI, format_url("users/<string:username>/statistics/")
)
api.add_resource(
UsersStatisticsAllAPI,
format_url("users/statistics/"),
)
# User RecommendedProjects endpoint
api.add_resource(
UsersRecommendedProjectsAPI,
format_url("users/<string:username>/recommended-projects/"),
)
# User Interests endpoint
api.add_resource(
UsersQueriesInterestsAPI,
format_url("users/<string:username>/queries/interests/"),
)
# Users openstreetmap endpoint
api.add_resource(
UsersOpenStreetMapAPI, format_url("users/<string:username>/openstreetmap/")
)
# System endpoint
api.add_resource(SystemDocsAPI, format_url("system/docs/json/"))
api.add_resource(SystemHeartbeatAPI, format_url("system/heartbeat/"))
api.add_resource(SystemLanguagesAPI, format_url("system/languages/"))
api.add_resource(SystemStatisticsAPI, format_url("system/statistics/"))
api.add_resource(
SystemAuthenticationLoginAPI, format_url("system/authentication/login/")
)
api.add_resource(
SystemAuthenticationCallbackAPI, format_url("system/authentication/callback/")
)
api.add_resource(
SystemAuthenticationEmailAPI, format_url("system/authentication/email/")
)
api.add_resource(
SystemImageUploadRestAPI,
format_url("system/image-upload/"),
methods=["POST"],
)
api.add_resource(
SystemApplicationsRestAPI,
format_url("system/authentication/applications/"),
methods=["POST", "GET"],
)
api.add_resource(
SystemApplicationsRestAPI,
format_url("system/authentication/applications/<string:application_key>/"),
endpoint="delete_application",
methods=["DELETE"],
)
api.add_resource(
SystemApplicationsRestAPI,
format_url("system/authentication/applications/<string:application_key>/"),
endpoint="check_application",
methods=["PATCH"],
)
api.add_resource(
SystemContactAdminRestAPI, format_url("system/contact-admin/"), methods=["POST"]
)