Files
web-page-backend/packs-lab/app/dsm_client.py
gahusb 7a7e3d1ce0 fix(packs-lab): 누락된 A1-A3 파일 복구 (Dockerfile + auth + DSM client + tests)
Tasks A1-A3 의 파일이 working tree에 있었으나 git에 stage되지 않은 상태에서
A4 commit이 진행되어 routes.py 의 import (auth, dsm_client)가 깨진 상태였음.
복구: 8 files 일괄 commit.

- packs-lab/Dockerfile
- packs-lab/app/__init__.py
- packs-lab/app/requirements.txt
- packs-lab/app/auth.py (HMAC verify_request_hmac + verify_upload_token)
- packs-lab/app/dsm_client.py (DSM 7.x Sharing.create wrapper)
- packs-lab/tests/{__init__.py, test_auth.py}
- packs-lab/pytest.ini
2026-05-02 08:53:26 +09:00

103 lines
3.1 KiB
Python

"""Synology DSM 7.x API 클라이언트.
각 호출 = login → 작업 → logout (세션 풀링은 v1.1+에서). 단일 컨테이너 + 동시성 낮음 가정.
- create_share_link(file_path, expires_in_sec) -> share URL
"""
import logging
import os
from datetime import datetime, timedelta, timezone
import httpx
logger = logging.getLogger("packs-lab.dsm")
DSM_HOST = os.getenv("DSM_HOST", "") # 예: https://gahusb.synology.me:5001
DSM_USER = os.getenv("DSM_USER", "")
DSM_PASS = os.getenv("DSM_PASS", "")
API_AUTH = "/webapi/auth.cgi"
API_SHARE = "/webapi/entry.cgi"
class DSMError(RuntimeError):
pass
async def _login(client: httpx.AsyncClient) -> str:
"""DSM 세션 sid 반환."""
if not all([DSM_HOST, DSM_USER, DSM_PASS]):
raise DSMError("DSM 환경변수 미설정")
r = await client.get(
f"{DSM_HOST}{API_AUTH}",
params={
"api": "SYNO.API.Auth",
"version": "7",
"method": "login",
"account": DSM_USER,
"passwd": DSM_PASS,
"session": "FileStation",
"format": "sid",
},
timeout=15.0,
)
r.raise_for_status()
data = r.json()
if not data.get("success"):
raise DSMError(f"DSM login 실패: {data.get('error')}")
return data["data"]["sid"]
async def _logout(client: httpx.AsyncClient, sid: str) -> None:
try:
await client.get(
f"{DSM_HOST}{API_AUTH}",
params={
"api": "SYNO.API.Auth",
"version": "7",
"method": "logout",
"session": "FileStation",
"_sid": sid,
},
timeout=10.0,
)
except Exception as e:
logger.warning("DSM logout 실패: %s", e)
async def create_share_link(file_path: str, expires_in_sec: int = 14400) -> tuple[str, datetime]:
"""파일 공유 링크 생성. 반환: (URL, expires_at).
file_path: NAS 절대경로 (예: /volume1/docker/webpage/media/packs/master/x.mp4)
expires_in_sec: 만료 (기본 4시간)
"""
expires_at = datetime.now(timezone.utc) + timedelta(seconds=expires_in_sec)
expire_time_ms = int(expires_at.timestamp() * 1000)
async with httpx.AsyncClient(verify=True) as client:
sid = await _login(client)
try:
r = await client.get(
f"{DSM_HOST}{API_SHARE}",
params={
"api": "SYNO.FileStation.Sharing",
"version": "3",
"method": "create",
"path": file_path,
"date_expired": expire_time_ms,
"_sid": sid,
},
timeout=15.0,
)
r.raise_for_status()
data = r.json()
if not data.get("success"):
raise DSMError(f"DSM Sharing.create 실패: {data.get('error')}")
links = data["data"]["links"]
if not links:
raise DSMError("Sharing 응답에 링크 없음")
url = links[0]["url"]
return url, expires_at
finally:
await _logout(client, sid)