feat: schedule assigned issues from mobile (#191)
CI / lint (pull_request) Successful in 17s
CI / build-frontend (pull_request) Successful in 5s

This commit is contained in:
timmy committed 2026-08-07 11:29:35 +00:00
1 parent 5406ee2297
commit c639b09aaf
11 files changed
+378 -6

No files matched your search

+34
View File
@@ -732,6 +732,39 @@ async def update_issue_labels(repository: str, number: int, label_ids: list[int]
}
async def update_assigned_issue_due_date(
repository: str, number: int, due_date: str | None
) -> dict:
path = f"repos/{repository}/issues/{number}"
login, issue = await _current_login_and_target(path)
if (
issue.get("state") != "open"
or isinstance(issue.get("pull_request"), dict)
or not _login_in_users(login, issue.get("assignees"))
):
raise IssueNotAvailableError("assigned issue not found")
payload = {"due_date": due_date} if due_date else {"unset_due_date": True}
response = await _get_client().patch(
f"/api/v1/{path}", headers=_auth(), json=payload
)
response.raise_for_status()
confirmed = response.json()
confirmed_due_date = confirmed.get("due_date") if isinstance(confirmed, dict) else None
if (
not isinstance(confirmed, dict)
or confirmed.get("number") != number
or confirmed_due_date != due_date
):
raise ValueError("Gitea did not confirm the issue due date update")
return {
"repository": repository,
"number": number,
"state": confirmed.get("state", "open"),
"due_date": confirmed_due_date,
}
async def issue_detail(repository: str, number: int) -> dict:
base = f"repos/{repository}/issues/{number}"
issue, comments = await asyncio.gather(
@@ -756,6 +789,7 @@ async def issue_detail(repository: str, number: int) -> dict:
"state": issue.get("state", "") if isinstance(issue.get("state"), str) else "",
"body": issue.get("body", "") if isinstance(issue.get("body"), str) else "",
"updated_at": issue.get("updated_at", "") if isinstance(issue.get("updated_at"), str) else "",
"due_date": issue.get("due_date") if isinstance(issue.get("due_date"), str) else None,
"url": _safe_web_url(issue.get("html_url")),
"labels": [
label["name"] for label in labels
+40 -2
View File
@@ -178,6 +178,14 @@ class IssueLabelUpdate(BaseModel):
label_ids: list[PositiveInt] = Field(max_length=20)
class IssueDueDateUpdate(BaseModel):
due_date: str | None = Field(
default=None,
pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$",
max_length=20,
)
class PullReviewComment(BaseModel):
path: str = Field(min_length=1, max_length=1_000)
body: str = Field(min_length=1, max_length=10_000)
@@ -286,7 +294,9 @@ def _context_payload(user_data, repo_data, issues_data, prs_data) -> dict:
labels=[label.get("name", "") for label in (i.get("labels") or []) if isinstance(label, dict)],
assignees=[assignee.get("login", "") for assignee in (i.get("assignees") or []) if isinstance(assignee, dict)],
repository=i["repository"].get("full_name", "") if isinstance(i.get("repository"), dict) else "",
updated_at=i.get("updated_at") or "", url=i["html_url"],
updated_at=i.get("updated_at") or "",
due_date=i.get("due_date") if isinstance(i.get("due_date"), str) else None,
url=i["html_url"],
)
for i in (issues_data or [])[:50]
if isinstance(i, dict)
@@ -324,7 +334,9 @@ def _normalize_work_items(stream: str, items: list[dict]) -> list[dict]:
labels=[label.get("name", "") for label in (item.get("labels") or []) if isinstance(label, dict)],
assignees=[assignee.get("login", "") for assignee in (item.get("assignees") or []) if isinstance(assignee, dict)],
repository=item["repository"].get("full_name", "") if isinstance(item.get("repository"), dict) else "",
updated_at=item.get("updated_at") or "", url=item["html_url"],
updated_at=item.get("updated_at") or "",
due_date=item.get("due_date") if isinstance(item.get("due_date"), str) else None,
url=item["html_url"],
).model_dump()
for item in items
if all(field in item for field in ("id", "number", "title", "state", "html_url"))
@@ -1126,6 +1138,32 @@ async def update_assigned_issue_content(
return JSONResponse(result)
@app.patch("/api/v1/repos/{owner}/{repo}/issues/{number}/due-date")
async def update_assigned_issue_due_date(
update: IssueDueDateUpdate,
owner: str,
repo: str,
number: int = PathParam(gt=0),
):
repository = f"{owner}/{repo}"
try:
result = await asyncio.wait_for(
gitea_proxy.update_assigned_issue_due_date(
repository, number, update.due_date
),
timeout=ISSUE_ACTION_TIMEOUT_SECONDS,
)
except gitea_proxy.IssueNotAvailableError:
raise HTTPException(status_code=404, detail="Assigned issue not found")
except Exception:
return JSONResponse(
{"error": "The due date could not be updated. Your selection is safe; please retry."},
status_code=503,
headers={"Retry-After": "1"},
)
return JSONResponse(result)
@app.get("/api/v1/repos/{owner}/{repo}/labels")
async def repository_labels(owner: str, repo: str):
repository = f"{owner}/{repo}"
+1
View File
@@ -26,6 +26,7 @@ class Issue(BaseModel):
assignees: list[str] = []
repository: str = ""
updated_at: str = ""
due_date: str | None = None
url: str