feat(phase 3h gitea writeback): close/reopen/comment/create from projax
- gitea pkg: CloseIssue, ReopenIssue, CreateIssue, AddComment + ErrForbidden
classification on 401/403. Client.do sets Content-Type on non-empty bodies.
- web handler: POST /i/{path}/issues/{close|reopen|comment|create}
- authorisation guard: repo form value must match a gitea-repo item_link
on the target item (rejects form-crafted writes to unrelated repos)
- HTMX re-renders issues_section partial after each action
- busts gitea per-repo cache (open + closed-recent) and dashboard 60s TTL
- templates: ✓ close button + reopen + collapsible comment box on every
issue row; "+ new issue" disclosure per repo
- design.md §6 retitled "Phase 2.d read; 3h writeback" with auth/perm
semantics + parked list
- 5 unit tests in gitea/, 5 integration tests in web/ covering happy paths
+ 403 → inline banner fallback
This commit is contained in:
@@ -256,16 +256,24 @@ m's CalDAV server lives at `dav.msbls.de/dav/calendars/m/` (SabreDAV, Basic auth
|
||||
|
||||
Env contract: `DAV_URL` (default `https://dav.msbls.de/dav/calendars/m/`), `DAV_USER`, `DAV_PASSWORD`. All three live in Dokploy secrets; missing → `/admin/caldav` renders a "not configured" notice and the detail page hides the Tasks section.
|
||||
|
||||
## 6. Gitea integration (Phase 2.d, v1: read-only)
|
||||
## 6. Gitea integration (Phase 2.d read; 3h writeback)
|
||||
|
||||
m's Gitea instance lives at `mgit.msbls.de` (token auth, automation account `mAi`). projax v1 reads but does not write:
|
||||
m's Gitea instance lives at `mgit.msbls.de` (token auth, automation account `mAi`). Phase 2.d landed read-only; Phase 3h extended it to read + write for the four most common operations:
|
||||
|
||||
- **Link model**: a `projax.item_links` row with `ref_type='gitea-repo'`, `ref_id='<owner>/<repo>'` (e.g. `m/projax`, `mAi/paliad`, `HL/mWorkRepo`). The Phase 1.5 backfill already populated this row for every `mai.projects` with a `repo` field. An item can carry multiple `gitea-repo` links — projax sums them on the detail page.
|
||||
- **Issues section** (item detail page, rendered when at least one `gitea-repo` link exists): per-repo block with open issues (`#N · title · labels · milestone · assignees · updated <rel>`), a `↗ Gitea repo` link in the header, and a disclosure for the last-30-days closed issues (up to 20). Title and number link out to `htmlURL` on Gitea (`target="_blank"`). Failed fetches (404, network) surface as a per-repo banner so one missing repo doesn't blank the section.
|
||||
- **Listing**: `GET /api/v1/repos/{owner}/{repo}/issues?state=open&type=issues&limit=50` for the open list; same shape with `state=closed&since=<-30d>&limit=20` for the recent-closed disclosure. `type=issues` filters PRs out server-side on Gitea ≥1.20; the client also drops any `pull_request != null` rawIssue as belt-and-braces.
|
||||
- **Caching**: per-process, in-memory TTL cache (~3 min) keyed by `owner/repo|state` so rendering the same detail page back-to-back does not hammer Gitea. No DB cache table at v1; a `projax.cached_issues` would land in 2.f if perf bites.
|
||||
- **Auth**: `Authorization: token <GITEA_TOKEN>`. The token is the **mAi** automation account (`GITEA_TOKEN_AI` in `.env.age`) — keeps projax's reads attributed to mAi for audit purposes, same as how every other automated worker talks to Gitea. Missing token + non-empty URL → fail-fast at boot.
|
||||
- **PR aggregation, issue writeback, webhook live updates**: parked. Writeback is Phase 2.e if m wants it; webhook-driven freshness is 2.f.
|
||||
- **Writeback (Phase 3h)** — four operations on the Issues section + dashboard Issues card:
|
||||
- **Close** an open issue (`PATCH /repos/{o}/{r}/issues/{n}` with `{"state":"closed"}`) — single click, no confirm modal (cheap to reopen).
|
||||
- **Reopen** a closed issue (same endpoint with `{"state":"open"}`).
|
||||
- **Comment** on an issue (`POST /repos/{o}/{r}/issues/{n}/comments` with `{"body":...}`).
|
||||
- **Create** a new issue under a linked repo (`POST /repos/{o}/{r}/issues` with `{"title":..., "body":...}`).
|
||||
- **Authorisation**: writeback handlers reject any `repo` form value that isn't linked to the item via a `gitea-repo` item_link. Prevents form-crafted writes against arbitrary repos.
|
||||
- **Token permission**: the mAi token (`GITEA_TOKEN_AI`) needs write scope on m's repos. A 401/403 surfaces as `gitea.ErrForbidden` and renders an inline "Gitea token lacks write access" banner so the page never breaks.
|
||||
- **Cache busting**: every successful writeback invalidates both the Gitea per-repo cache entries (`{repo}|open` + `{repo}|closed-recent`) and the dashboard 60s TTL (all keys) so the next render reflects the upstream change.
|
||||
- **Parked further**: PR creation, label edit (folded in only if cheap), issue title/body edit, comment edit/delete, webhook live updates, cross-repo bulk ops, issue templates.
|
||||
|
||||
Env contract: `GITEA_URL` (e.g. `https://mgit.msbls.de`, no `/api/v1` suffix), `GITEA_TOKEN`. Both live in Dokploy secrets; `GITEA_URL` unset → integration off cleanly (Issues section just doesn't render). `GITEA_URL` set but `GITEA_TOKEN` missing → refuse to start.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user