import traceback from sanic import Sanic, response from uuid import uuid4 import traceback as _traceback from app.core.logger import make_log app = Sanic(__name__) from app.api.middleware import attach_user_to_request, close_db_session, close_request_handler app.register_middleware(attach_user_to_request, "request") app.register_middleware(close_db_session, "response") from app.api.routes._index import s_index, s_favicon from app.api.routes._system import s_api_v1_node, s_api_system_version, s_api_system_send_status, s_api_v1_node_friendly from app.api.routes.network import ( s_api_v1_network_info, s_api_v1_network_nodes, s_api_v1_network_handshake, ) from app.api.routes.auth import s_api_v1_auth_twa, s_api_v1_auth_select_wallet, s_api_v1_auth_me from app.api.routes.statics import s_api_tonconnect_manifest, s_api_platform_metadata from app.api.routes.node_storage import s_api_v1_storage_post, s_api_v1_storage_get, \ s_api_v1_storage_decode_cid from app.api.routes.progressive_storage import s_api_v1_5_storage_get, s_api_v1_5_storage_post from app.api.routes.upload_tus import s_api_v1_upload_tus_hook from app.api.routes.account import s_api_v1_account_get from app.api.routes._blockchain import s_api_v1_blockchain_send_new_content_message, \ s_api_v1_blockchain_send_purchase_content_message from app.api.routes.content import s_api_v1_content_list, s_api_v1_content_view, s_api_v1_content_friendly_list, s_api_v1_5_content_list from app.api.routes.content_index import s_api_v1_content_index, s_api_v1_content_delta from app.api.routes.derivatives import s_api_v1_content_derivatives from app.api.routes.admin import ( s_api_v1_admin_blockchain, s_api_v1_admin_cache_cleanup, s_api_v1_admin_cache_setlimits, s_api_v1_admin_login, s_api_v1_admin_logout, s_api_v1_admin_node_setrole, s_api_v1_admin_nodes, s_api_v1_admin_overview, s_api_v1_admin_status, s_api_v1_admin_storage, s_api_v1_admin_sync_setlimits, s_api_v1_admin_system, s_api_v1_admin_uploads, ) from app.api.routes.tonconnect import s_api_v1_tonconnect_new, s_api_v1_tonconnect_logout from app.api.routes.keys import s_api_v1_keys_request from app.api.routes.sync import s_api_v1_sync_pin, s_api_v1_sync_status from app.api.routes.upload_status import s_api_v1_upload_status app.add_route(s_index, "/", methods=["GET", "OPTIONS"]) app.add_route(s_favicon, "/favicon.ico", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_node, "/api/v1/node", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_node_friendly, "/api/v1/nodeFriendly", methods=["GET", "OPTIONS"]) app.add_route(s_api_system_version, "/api/system.version", methods=["GET", "OPTIONS"]) app.add_route(s_api_system_send_status, "/api/system.sendStatus", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_network_info, "/api/v1/network.info", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_network_nodes, "/api/v1/network.nodes", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_network_handshake, "/api/v1/network.handshake", methods=["POST", "OPTIONS"]) app.add_route(s_api_tonconnect_manifest, "/api/tonconnect-manifest.json", methods=["GET", "OPTIONS"]) app.add_route(s_api_platform_metadata, "/api/platform-metadata.json", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_auth_twa, "/api/v1/auth.twa", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_auth_me, "/api/v1/auth.me", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_auth_select_wallet, "/api/v1/auth.selectWallet", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_tonconnect_new, "/api/v1/tonconnect.new", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_tonconnect_logout, "/api/v1/tonconnect.logout", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_5_storage_post, "/api/v1.5/storage", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_5_storage_get, "/api/v1.5/storage/", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_storage_post, "/api/v1/storage", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_storage_get, "/api/v1/storage/", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_storage_decode_cid, "/api/v1/storage.decodeContentId/", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_account_get, "/api/v1/account", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_blockchain_send_new_content_message, "/api/v1/blockchain.sendNewContentMessage", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_blockchain_send_purchase_content_message, "/api/v1/blockchain.sendPurchaseContentMessage", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_content_list, "/api/v1/content.list", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_content_view, "/api/v1/content.view/", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_content_friendly_list, "/api/v1/content.friendlyList", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_5_content_list, "/api/v1.5/content.list", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_content_index, "/api/v1/content.index", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_content_delta, "/api/v1/content.delta", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_content_derivatives, "/api/v1/content.derivatives", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_login, "/api/v1/admin.login", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_admin_logout, "/api/v1/admin.logout", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_admin_overview, "/api/v1/admin.overview", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_storage, "/api/v1/admin.storage", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_uploads, "/api/v1/admin.uploads", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_system, "/api/v1/admin.system", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_blockchain, "/api/v1/admin.blockchain", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_node_setrole, "/api/v1/admin.node.setRole", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_admin_nodes, "/api/v1/admin.nodes", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_status, "/api/v1/admin.status", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_admin_cache_setlimits, "/api/v1/admin.cache.setLimits", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_admin_cache_cleanup, "/api/v1/admin.cache.cleanup", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_admin_sync_setlimits, "/api/v1/admin.sync.setLimits", methods=["POST", "OPTIONS"]) # tusd HTTP hooks app.add_route(s_api_v1_upload_tus_hook, "/api/v1/upload.tus-hook", methods=["POST", "OPTIONS"]) # Keys auto-grant app.add_route(s_api_v1_keys_request, "/api/v1/keys.request", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_sync_pin, "/api/v1/sync.pin", methods=["POST", "OPTIONS"]) app.add_route(s_api_v1_sync_status, "/api/v1/sync.status", methods=["GET", "OPTIONS"]) app.add_route(s_api_v1_upload_status, "/api/v1/upload.status/", methods=["GET", "OPTIONS"]) @app.exception(BaseException) async def s_handle_exception(request, exception): # Correlate error to request session_id = getattr(request.ctx, 'session_id', None) or uuid4().hex[:16] error_id = uuid4().hex[:8] status = 500 code = type(exception).__name__ message = "Internal HTTP Error" try: raise exception except AssertionError as e: status = 400 code = 'AssertionError' message = str(e) or 'Bad Request' except BaseException as e: # keep default 500, but expose exception message to aid debugging message = str(e) or message # Build structured log with full context and traceback try: tb = _traceback.format_exc() user_id = getattr(getattr(request.ctx, 'user', None), 'id', None) log_ctx = { 'sid': session_id, 'eid': error_id, 'path': request.path, 'method': request.method, 'query': dict(request.args) if hasattr(request, 'args') else {}, 'user_id': user_id, 'remote': (request.headers.get('X-Forwarded-For') or request.remote_addr or request.ip), 'code': code, 'message': message, 'traceback': tb, } make_log('http_exception', 'API exception', level='error', **log_ctx) except BaseException: pass # Return enriched error response for the client payload = { 'error': True, 'code': code, 'message': message, 'session_id': session_id, 'error_id': error_id, 'path': request.path, 'method': request.method, } response_buffer = response.json(payload, status=status) response_buffer = await close_db_session(request, response_buffer) return response_buffer