from __future__ import annotations from datetime import datetime from sanic import response from sqlalchemy import select from app.core.ipfs_client import pin_add, pin_ls from app.core.logger import make_log from app.core.models.content_v3 import EncryptedContent, IpfsSync from app.core.network.nodesig import verify_request from app.core.network.guard import check_rate_limit async def s_api_v1_sync_pin(request): # Rate limit per IP and require NodeSig for POST remote_ip = (request.headers.get('X-Forwarded-For') or request.remote_addr or request.ip or '').split(',')[0].strip() if not check_rate_limit(request.app.ctx.memory, remote_ip): return response.json({"error": "RATE_LIMIT"}, status=429) ok, node_id, reason = verify_request(request, request.app.ctx.memory) if not ok: return response.json({"error": reason or "UNAUTHORIZED"}, status=401) data = request.json or {} cid = data.get("encrypted_cid") if not cid: return response.json({"error": "BAD_REQUEST"}, status=400) session = request.ctx.db_session row = (await session.execute(select(EncryptedContent).where(EncryptedContent.encrypted_cid == cid))).scalars().first() if not row: # create record with minimal info (unknown meta) row = EncryptedContent( encrypted_cid=cid, title=cid, description="", content_type="application/octet-stream", preview_enabled=False, ) session.add(row) await session.flush() sync = (await session.execute(select(IpfsSync).where(IpfsSync.content_id == row.id))).scalars().first() if not sync: sync = IpfsSync(content_id=row.id, pin_state='queued') session.add(sync) await session.flush() try: await pin_add(cid, recursive=True) sync.pin_state = 'pinned' sync.pinned_at = datetime.utcnow() except Exception as e: make_log("sync", f"pin failed: {e}", level="error") sync.pin_state = 'failed' sync.pin_error = str(e) await session.commit() return response.json({"ok": True, "state": sync.pin_state}) async def s_api_v1_sync_status(request): cid = request.args.get("cid") if not cid: return response.json({"error": "BAD_REQUEST"}, status=400) try: info = await pin_ls(cid) state = 'pinned' if info else 'not_pinned' except Exception: state = 'not_pinned' info = {} return response.json({"cid": cid, "state": state, "info": info})