feat(0.31.0): description éditable, pièces jointes d'issues, clic carte fiable
CI / test (push) Successful in 2m10s
CI / test (push) Successful in 2m10s
- Tiroir : édition du titre et de la description (PATCH forge, journal 'edited', EVENT_TYPES étendu), création avec description dépliable. - Pièces jointes Gitea de bout en bout : client list/upload/delete/download (le téléchargement suit browser_download_url — l'endpoint asset ne sert que du JSON, attrapé au smoke réel), routes proxifiées avec CSRF et plafond 25 Mio, UI liste/envoi/suppression ; GitHub : supports_attachments=False, message propre. - Clic carte en délégation d'événement : les écouteurs par carte mouraient au premier rafraîchissement live. - 261 tests verts (7 nouveaux, palier 21) ; cycle réel complet validé sur une issue jetable du vrai Gitea (création, édition, envoi, téléchargement, suppression).
This commit is contained in:
@@ -48,6 +48,7 @@ class GiteaClient:
|
||||
"""
|
||||
|
||||
kind = "gitea"
|
||||
supports_attachments = True
|
||||
api_suffix = "/api/v1"
|
||||
auth_scheme = "token"
|
||||
|
||||
@@ -503,6 +504,87 @@ class GiteaClient:
|
||||
self._cache_clear()
|
||||
return r.json()
|
||||
|
||||
async def update_issue(
|
||||
self,
|
||||
owner: str,
|
||||
repo: str,
|
||||
number: int,
|
||||
title: str | None = None,
|
||||
body: str | None = None,
|
||||
) -> dict:
|
||||
"""Edit an issue's title and/or body (0.31.0)."""
|
||||
payload: dict = {}
|
||||
if title is not None:
|
||||
payload["title"] = title
|
||||
if body is not None:
|
||||
payload["body"] = body
|
||||
if not payload:
|
||||
return await self.get_issue(owner, repo, number)
|
||||
r = await self.client.patch(
|
||||
f"/repos/{owner}/{repo}/issues/{number}", json=payload
|
||||
)
|
||||
r.raise_for_status()
|
||||
self._cache_clear()
|
||||
return r.json()
|
||||
|
||||
# ── Pièces jointes des issues (0.31.0) ────────────────
|
||||
|
||||
async def list_issue_assets(
|
||||
self, owner: str, repo: str, number: int
|
||||
) -> list[dict]:
|
||||
"""Attachments of an issue (Gitea assets)."""
|
||||
return await self._get(
|
||||
f"/repos/{owner}/{repo}/issues/{number}/assets"
|
||||
)
|
||||
|
||||
async def upload_issue_asset(
|
||||
self,
|
||||
owner: str,
|
||||
repo: str,
|
||||
number: int,
|
||||
filename: str,
|
||||
content: bytes,
|
||||
content_type: str = "application/octet-stream",
|
||||
) -> dict:
|
||||
"""Attach a file to an issue; returns the asset record."""
|
||||
r = await self.client.post(
|
||||
f"/repos/{owner}/{repo}/issues/{number}/assets",
|
||||
files={"attachment": (filename, content, content_type)},
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
async def delete_issue_asset(
|
||||
self, owner: str, repo: str, number: int, asset_id: int
|
||||
) -> None:
|
||||
r = await self.client.delete(
|
||||
f"/repos/{owner}/{repo}/issues/{number}/assets/{asset_id}"
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
async def download_issue_asset(
|
||||
self, owner: str, repo: str, number: int, asset_id: int
|
||||
) -> tuple[bytes, str]:
|
||||
"""(content, content_type) of one attachment.
|
||||
|
||||
Gitea nuance (vérifiée en réel sur 1.27.3): GET on the
|
||||
asset endpoint returns its JSON record, NOT the bytes —
|
||||
the file itself lives at `browser_download_url`
|
||||
(/attachments/<uuid>), fetched here with the connection's
|
||||
token so private repos work too.
|
||||
"""
|
||||
record = await self._get(
|
||||
f"/repos/{owner}/{repo}/issues/{number}/assets/{asset_id}"
|
||||
)
|
||||
url = record.get("browser_download_url") if isinstance(record, dict) else None
|
||||
if not url:
|
||||
raise ValueError("La forge ne fournit pas d'URL de téléchargement")
|
||||
r = await self.client.get(url)
|
||||
r.raise_for_status()
|
||||
return r.content, r.headers.get(
|
||||
"content-type", "application/octet-stream"
|
||||
)
|
||||
|
||||
async def set_issue_state(
|
||||
self, owner: str, repo: str, issue_number: int, state: str
|
||||
) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user