collin/anvil
1496ee952d9307f84ee075ad02a3feea1a185915 / TODO.md
| 1 | # Todo |
| 2 | |
| 3 | - [ ] pull mirror (maybe): a repo that virtually mirrors a GitHub repo |
| 4 | - just displays it here — periodically fetched, read-only on the anvil side |
| 5 | - [ ] pull requests (gix merge) |
| 6 | - [ ] webhooks (mind the SSRF item in `docs/untrusted-mode.md`) |
| 7 | |
| 8 | ## Edit files in the web UI |
| 9 | |
| 10 | Edit a file in the browser and have anvil make a proper commit (author = the |
| 11 | logged-in user, sensible message), written straight onto the branch with gix — |
| 12 | no working tree. The new commit just advances the branch tip, so anyone who |
| 13 | pushed earlier can fast-forward pull it. |
| 14 | |
| 15 | - [x] start minimal: an "Edit" button on the blob page → textarea → commit; |
| 16 | commits build the tree/commit objects via gix and move the ref (reject if the |
| 17 | branch moved under us — no non-fast-forward clobber). `anvil-git/src/edit.rs` |
| 18 | does the CAS commit; `ui.rs` `edit_form`/`edit_submit` wire the page. |
| 19 | - [x] a structured way to add items to `TODO.md` — an "Add task" form that |
| 20 | appends a ticket (`## title`, the richer card style) to the right section per |
| 21 | the todo-md round-trip rules (`todomd::add_task` / `task_sections`), rather |
| 22 | than hand-editing the raw file |
| 23 | - [ ] then richer editing: a real markdown editor with a live render preview |
| 24 | (reuse `render_markdown`) before committing |
| 25 | |
| 26 | ## Image uploads (attachments stored outside git) |
| 27 | |
| 28 | Upload an image in the web editor and link to it from the markdown without the |
| 29 | blob ever entering git history. Stored content-addressed per repo and served |
| 30 | back; the file only carries the URL. |
| 31 | |
| 32 | - [x] store: content-addressed blobs at `data/attachments/{repo_id}/{sha256}`, |
| 33 | deduped per repo; `Attachment` model maps repo_id/hash → content-type, size, |
| 34 | uploader, created-at. Kept out of `repositories/` so it's never a git object. |
| 35 | (`anvil-core`: `attachments`, `storage::attachment_path`, schema shim.) |
| 36 | - [x] serve: `GET /{owner}/{repo}/-/attachments/{hash}`, read-access gated |
| 37 | (private repos stay private), immutable cache + `nosniff` + locked-down CSP. |
| 38 | - [x] upload: `POST /{owner}/{repo}/-/attachments` behind write-access + CSRF |
| 39 | (`X-CSRF-Token` header), magic-byte sniffed to png/jpeg/gif/webp (SVG |
| 40 | rejected), capped by `http.attachment_max_mb`, returns the markdown to splice. |
| 41 | - [x] editor UX: paste or drop an image in the file editor → background upload → |
| 42 | `` inserted at the cursor. |
| 43 | - [x] caps: per-repo attachment quota (`http.attachment_quota_mb`, 0 = |
| 44 | unlimited) — a new upload over the cap is rejected; deduped re-uploads are |
| 45 | always free. (Reject, not evict: evicting would break live Markdown links.) |
| 46 | - [ ] within-repo reclaim: an orphan sweep (delete attachments no committed file |
| 47 | references) and/or a per-attachment delete action — the recourse once a repo |
| 48 | hits its quota. Deferred: deletion is destructive and "orphaned" is fuzzy |
| 49 | (tip-only vs any-ref), so it wants its own design pass. |
| 50 | - [ ] remove a repo's attachment + artifact dirs on repo delete — blocked: there |
| 51 | is no repo-delete path yet (only the create-rollback uses it). |
| 52 | |
| 53 | ## Admin: site disk-usage dashboard |
| 54 | |
| 55 | - [x] `/-/admin/usage` (admin-only; 404 for everyone else, nav link for admins): |
| 56 | actual on-disk bytes per user, broken down by content type (repositories / CI |
| 57 | artifacts / attachments) with column + grand totals. `anvil-core::usage` |
| 58 | walks the stores; `storage::dir_size` sums them. |
| 59 | - [ ] maybe: per-repo drill-down, and a cheap cached/periodic variant if the |
| 60 | on-demand disk walk gets slow on large instances. |