2016-08-14 10:35:14 +00:00
|
|
|
import logging
|
2020-06-05 22:03:37 +00:00
|
|
|
import os
|
2017-01-07 10:59:43 +00:00
|
|
|
import threading
|
2020-06-05 22:03:37 +00:00
|
|
|
import time
|
|
|
|
from typing import Any, Callable, Type
|
2017-02-04 00:08:12 +00:00
|
|
|
|
2016-08-14 10:35:14 +00:00
|
|
|
import coloredlogs
|
2017-02-04 00:08:12 +00:00
|
|
|
import sqlalchemy as sa
|
2016-08-14 18:30:48 +00:00
|
|
|
import sqlalchemy.orm.exc
|
2020-06-05 22:03:37 +00:00
|
|
|
|
|
|
|
from szurubooru import api, config, db, errors, middleware, rest
|
2020-03-08 02:39:22 +00:00
|
|
|
from szurubooru.func.file_uploads import purge_old_uploads
|
2021-01-05 18:00:13 +00:00
|
|
|
from szurubooru.func.posts import (
|
|
|
|
update_all_md5_checksums,
|
|
|
|
update_all_post_signatures,
|
|
|
|
)
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _map_error(
|
2020-06-05 22:03:37 +00:00
|
|
|
ex: Exception, target_class: Type[rest.errors.BaseHttpError], title: str
|
|
|
|
) -> rest.errors.BaseHttpError:
|
2016-09-10 08:14:40 +00:00
|
|
|
return target_class(
|
2016-09-10 08:37:28 +00:00
|
|
|
name=type(ex).__name__,
|
2016-09-10 08:14:40 +00:00
|
|
|
title=title,
|
|
|
|
description=str(ex),
|
2020-06-05 22:03:37 +00:00
|
|
|
extra_fields=getattr(ex, "extra_fields", {}),
|
|
|
|
)
|
2016-09-10 08:14:40 +00:00
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_auth_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpForbidden, "Authentication error")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_validation_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpBadRequest, "Validation error")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_search_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpBadRequest, "Search error")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_integrity_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpConflict, "Integrity violation")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_not_found_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpNotFound, "Not found")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_processing_error(ex: Exception) -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
raise _map_error(ex, rest.errors.HttpBadRequest, "Processing error")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_third_party_error(ex: Exception) -> None:
|
2017-02-02 17:21:21 +00:00
|
|
|
raise _map_error(
|
2020-06-05 22:03:37 +00:00
|
|
|
ex, rest.errors.HttpInternalServerError, "Server configuration error"
|
|
|
|
)
|
2017-02-02 17:21:21 +00:00
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _on_stale_data_error(_ex: Exception) -> None:
|
2016-08-14 18:30:48 +00:00
|
|
|
raise rest.errors.HttpConflict(
|
2020-06-05 22:03:37 +00:00
|
|
|
name="IntegrityError",
|
|
|
|
title="Integrity violation",
|
2016-09-10 08:37:28 +00:00
|
|
|
description=(
|
2020-06-05 22:03:37 +00:00
|
|
|
"Someone else modified this in the meantime. " "Please try again."
|
|
|
|
),
|
|
|
|
)
|
2016-08-14 18:30:48 +00:00
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def validate_config() -> None:
|
2020-06-05 22:03:37 +00:00
|
|
|
"""
|
2016-08-14 10:35:14 +00:00
|
|
|
Check whether config doesn't contain errors that might prove
|
|
|
|
lethal at runtime.
|
2020-06-05 22:03:37 +00:00
|
|
|
"""
|
2016-08-14 10:35:14 +00:00
|
|
|
from szurubooru.func.auth import RANK_MAP
|
2020-06-05 22:03:37 +00:00
|
|
|
|
|
|
|
for privilege, rank in config.config["privileges"].items():
|
2016-08-14 10:35:14 +00:00
|
|
|
if rank not in RANK_MAP.values():
|
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"Rank %r for privilege %r is missing" % (rank, privilege)
|
|
|
|
)
|
|
|
|
if config.config["default_rank"] not in RANK_MAP.values():
|
2016-08-14 10:35:14 +00:00
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"Default rank %r is not on the list of known ranks"
|
|
|
|
% (config.config["default_rank"])
|
|
|
|
)
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
for key in ["data_url", "data_dir"]:
|
2016-08-14 10:35:14 +00:00
|
|
|
if not config.config[key]:
|
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"Service is not configured: %r is missing" % key
|
|
|
|
)
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
if not os.path.isabs(config.config["data_dir"]):
|
|
|
|
raise errors.ConfigError("data_dir must be an absolute path")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
if not config.config["database"]:
|
|
|
|
raise errors.ConfigError("Database is not configured")
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2020-08-13 23:14:14 +00:00
|
|
|
if config.config["webhooks"] and not isinstance(
|
|
|
|
config.config["webhooks"], list
|
|
|
|
):
|
|
|
|
raise errors.ConfigError("Webhooks must be provided as a list of URLs")
|
|
|
|
|
2020-06-05 22:03:37 +00:00
|
|
|
if config.config["smtp"]["host"]:
|
|
|
|
if not config.config["smtp"]["port"]:
|
|
|
|
raise errors.ConfigError("SMTP host is set but port is not set")
|
|
|
|
if not config.config["smtp"]["user"]:
|
2019-07-22 23:51:22 +00:00
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"SMTP host is set but username is not set"
|
|
|
|
)
|
|
|
|
if not config.config["smtp"]["pass"]:
|
2019-07-22 23:51:22 +00:00
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"SMTP host is set but password is not set"
|
|
|
|
)
|
|
|
|
if not config.config["smtp"]["from"]:
|
2019-07-22 23:51:22 +00:00
|
|
|
raise errors.ConfigError(
|
2020-06-05 22:03:37 +00:00
|
|
|
"From address must be set to use mail-based password reset"
|
|
|
|
)
|
2019-07-22 23:51:22 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2020-03-08 02:39:22 +00:00
|
|
|
def purge_old_uploads_daemon() -> None:
|
2017-01-07 10:59:43 +00:00
|
|
|
while True:
|
|
|
|
try:
|
2020-03-08 02:39:22 +00:00
|
|
|
purge_old_uploads()
|
2017-01-07 10:59:43 +00:00
|
|
|
except Exception as ex:
|
|
|
|
logging.exception(ex)
|
|
|
|
time.sleep(60 * 5)
|
|
|
|
|
|
|
|
|
2021-01-05 18:00:13 +00:00
|
|
|
_live_migrations = (
|
|
|
|
update_all_post_signatures,
|
|
|
|
update_all_md5_checksums,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def create_app() -> Callable[[Any, Any], Any]:
|
2021-11-29 23:39:34 +00:00
|
|
|
"""Create a WSGI compatible App object."""
|
2016-08-14 10:35:14 +00:00
|
|
|
validate_config()
|
2020-06-05 22:03:37 +00:00
|
|
|
coloredlogs.install(fmt="[%(asctime)-15s] %(name)s %(message)s")
|
|
|
|
if config.config["debug"]:
|
|
|
|
logging.getLogger("szurubooru").setLevel(logging.INFO)
|
|
|
|
if config.config["show_sql"]:
|
|
|
|
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
2016-08-14 10:35:14 +00:00
|
|
|
|
2021-01-05 18:00:13 +00:00
|
|
|
threading.Thread(target=purge_old_uploads_daemon, daemon=True).start()
|
2017-02-02 18:39:30 +00:00
|
|
|
|
2021-01-05 18:00:13 +00:00
|
|
|
for migration in _live_migrations:
|
|
|
|
threading.Thread(target=migration, daemon=False).start()
|
2020-03-08 02:39:22 +00:00
|
|
|
|
2020-03-08 01:43:20 +00:00
|
|
|
db.session.commit()
|
2016-11-27 17:42:14 +00:00
|
|
|
|
2016-08-14 10:35:14 +00:00
|
|
|
rest.errors.handle(errors.AuthError, _on_auth_error)
|
|
|
|
rest.errors.handle(errors.ValidationError, _on_validation_error)
|
|
|
|
rest.errors.handle(errors.SearchError, _on_search_error)
|
|
|
|
rest.errors.handle(errors.IntegrityError, _on_integrity_error)
|
|
|
|
rest.errors.handle(errors.NotFoundError, _on_not_found_error)
|
|
|
|
rest.errors.handle(errors.ProcessingError, _on_processing_error)
|
2017-02-02 17:21:21 +00:00
|
|
|
rest.errors.handle(errors.ThirdPartyError, _on_third_party_error)
|
2017-02-04 00:08:12 +00:00
|
|
|
rest.errors.handle(sa.orm.exc.StaleDataError, _on_stale_data_error)
|
2016-08-14 10:35:14 +00:00
|
|
|
|
|
|
|
return rest.application
|
2017-01-20 22:51:51 +00:00
|
|
|
|
|
|
|
|
2020-06-05 14:02:18 +00:00
|
|
|
app = create_app()
|