feat: follow pull requests from mobile Search (Closes #1307)
CI / lint (pull_request) Successful in 3m34s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m17s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-23 13:41:33 +00:00
1 parent 41c28cc70a
commit 7131792ded
23 files changed
+271 -83

No files matched your search

+19 -4
View File
@@ -10,6 +10,7 @@ from src.state_encryption import PrivateStateCipher, PrivateStateEncryptionError
_REPOSITORY = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
_STATES = {"open", "closed"}
_KINDS = {"issue", "pull"}
class FollowingStore:
@@ -74,6 +75,9 @@ class FollowingStore:
number = raw.get("number")
if not isinstance(number, int) or isinstance(number, bool) or number < 1:
raise ValueError("number is invalid")
kind = raw.get("kind", "issue")
if kind not in _KINDS:
raise ValueError("kind is invalid")
title = raw.get("title")
if not isinstance(title, str) or not title.strip() or len(title.strip()) > 300:
raise ValueError("title is invalid")
@@ -95,6 +99,7 @@ class FollowingStore:
raise ValueError("last seen update is invalid")
return {
"repository": repository,
"kind": kind,
"number": number,
"title": title.strip(),
"state": state,
@@ -117,8 +122,8 @@ class FollowingStore:
return {"revision": snapshot["revision"], "items": changed + unchanged}
@staticmethod
def _identity(item: dict) -> tuple[str, int]:
return item["repository"].lower(), item["number"]
def _identity(item: dict) -> tuple[str, str, int]:
return item.get("kind", "issue"), item["repository"].lower(), item["number"]
def get(self, login: str) -> dict:
login = self._login(login)
@@ -226,10 +231,20 @@ class FollowingStore:
)
return self._present({"revision": revision, "items": items})
def acknowledge(self, login: str, repository: str, number: int, updated_at: str) -> dict:
def acknowledge(
self,
login: str,
repository: str,
number: int,
updated_at: str,
*,
kind: str = "issue",
) -> dict:
"""Mark only the exact upstream revision successfully opened by the operator."""
login = self._login(login)
identity = (str(repository).lower(), number)
if kind not in _KINDS:
raise ValueError("kind is invalid")
identity = (kind, str(repository).lower(), number)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute(
+7 -4
View File
@@ -2623,11 +2623,12 @@ async def get_following(response: Response):
async def refresh_item(item: dict) -> dict:
async with semaphore:
preview = await asyncio.wait_for(
gitea_proxy.work_preview(item["repository"], "issue", item["number"]),
gitea_proxy.work_preview(item["repository"], item["kind"], item["number"]),
timeout=GLOBAL_SEARCH_TIMEOUT_SECONDS,
)
return store._normalize_item({
"repository": item["repository"],
"kind": item["kind"],
"number": item["number"],
"title": preview.get("title", ""),
"state": preview.get("state", ""),
@@ -2658,6 +2659,7 @@ async def acknowledge_following_revision(
owner: str,
repo: str,
number: int = PathParam(gt=0),
kind: Literal["issue", "pull"] = Query(default="issue"),
):
login = await _confirmed_login()
try:
@@ -2667,6 +2669,7 @@ async def acknowledge_following_revision(
f"{owner}/{repo}",
number,
payload.updated_at,
kind=kind,
)
except ValueError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
@@ -3743,9 +3746,8 @@ async def _search_preview_subscription_target(
repository = f"{owner}/{repo}"
preview = await gitea_proxy.work_preview(repository, kind, number)
if (
kind != "issue"
or preview.get("repository") != repository
or preview.get("kind") != "issue"
preview.get("repository") != repository
or preview.get("kind") != kind
or preview.get("number") != number
or preview.get("state") not in ({"open", "closed"} if allow_closed else {"open"})
):
@@ -3797,6 +3799,7 @@ async def mutate_global_search_preview_subscription(
store = _following_store()
following_item = {
"repository": repository,
"kind": kind,
"number": number,
"title": preview.get("title", ""),
"state": preview.get("state", ""),