71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
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})
|