199 lines
10 KiB
Python
199 lines
10 KiB
Python
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.network_events import s_api_v1_network_events
|
|
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, s_api_v1_storage_fetch, s_api_v1_storage_proxy
|
|
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_events,
|
|
s_api_v1_admin_licenses,
|
|
s_api_v1_admin_login,
|
|
s_api_v1_admin_logout,
|
|
s_api_v1_admin_users_setadmin,
|
|
s_api_v1_admin_node_setrole,
|
|
s_api_v1_admin_nodes,
|
|
s_api_v1_admin_overview,
|
|
s_api_v1_admin_stars,
|
|
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,
|
|
s_api_v1_admin_users,
|
|
s_api_v1_admin_network,
|
|
s_api_v1_admin_network_config,
|
|
s_api_v1_admin_network_config_set,
|
|
)
|
|
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
|
|
from app.api.routes.metrics import s_api_metrics
|
|
from app.api.routes.dht import s_api_v1_dht_get, s_api_v1_dht_put
|
|
|
|
|
|
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_v1_network_events, "/api/v1/network.events", methods=["GET", "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/<file_hash>", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_storage_fetch, "/api/v1/storage.fetch/<file_hash>", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_storage_proxy, "/api/v1/storage.proxy/<file_hash>", 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/<file_hash>", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_storage_decode_cid, "/api/v1/storage.decodeContentId/<content_id>", 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/<content_address>", 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_users, "/api/v1/admin.users", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_users_setadmin, "/api/v1/admin.users.setAdmin", methods=["POST", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_licenses, "/api/v1/admin.licenses", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_stars, "/api/v1/admin.stars", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_events, "/api/v1/admin.events", 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"])
|
|
app.add_route(s_api_v1_admin_network, "/api/v1/admin.network", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_network_config, "/api/v1/admin.network.config", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_admin_network_config_set, "/api/v1/admin.network.config.set", 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/<upload_id>", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_metrics, "/metrics", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_dht_get, "/api/v1/dht.get", methods=["GET", "OPTIONS"])
|
|
app.add_route(s_api_v1_dht_put, "/api/v1/dht.put", methods=["POST", "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
|