Add web UI: login, dashboard, and share management (templ + Tailwind + htmx)
New internal/web package mounted at /ui/, separate from DAV Basic Auth: - Cookie-based sessions (opaque random tokens in a new web_sessions SQLite table, internal/db/sessions.go), checked against the same cfg.Users/bcrypt credentials as DAV Basic Auth. - Dashboard listing the logged-in user's own calendars/address books, who they're shared with, and what's shared with them. - Share/unshare directly from the dashboard, updated in place via htmx partial swaps (POST to create/update, DELETE to revoke). Always verifies the resource actually belongs to the logged-in user before granting a share. - Templates written in templ (internal/web/templates/*.templ, generated *_templ.go committed), styled with Tailwind CSS v4 (web/input.css, compiled to web/static/app.css), with htmx vendored as a static file for the dynamic bits. Both are embedded into the binary at build time (web/staticassets.go) so the compiled server has no Node.js/web/ runtime dependency. - Wired into cmd/server/main.go at /ui/ alongside the existing /cal/, /card/, /files/ routes; welcome page links to it. - Tests: internal/web/server_test.go covers login success/failure, the login-required redirect, dashboard rendering, share/unshare including the htmx-v2-sends-DELETE-params-as-query-string quirk, and rejecting shares of resources the user doesn't own. - Docs: README (new 'Web UI' section, updated sharing section, project layout, dependencies) and copilot-instructions updated accordingly. Makefile: new templ-generate/web-deps/web-css targets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
A self-hosted CalDAV, CardDAV, and WebDAV server written in Go, backed by a
|
||||
filesystem store, with calendar/address book sharing grants tracked in a
|
||||
small SQLite database. HTTP Basic Auth (bcrypt) with per-user isolated
|
||||
collections.
|
||||
collections. A small server-rendered web UI (templ + Tailwind + htmx) at
|
||||
`/ui/` lets users log in and manage their shares.
|
||||
|
||||
## Build, test, lint
|
||||
|
||||
@@ -14,11 +15,14 @@ make test # go test ./... -v -race
|
||||
make lint # golangci-lint run ./...
|
||||
make tidy # go mod tidy
|
||||
go test ./internal/store/ -run TestStoreRoundTrip -v # single test
|
||||
make templ-generate # regenerate *_templ.go after editing internal/web/templates/*.templ
|
||||
make web-css # templ-generate + rebuild web/static/app.css (needs `make web-deps` once, Node.js/npm)
|
||||
```
|
||||
|
||||
Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`,
|
||||
`internal/db/shares_test.go`, `internal/caldav/backend_test.go`,
|
||||
`internal/carddav/backend_test.go`.
|
||||
`internal/carddav/backend_test.go`, `internal/web/server_test.go`,
|
||||
`tools/nidusctl/main_test.go`.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -116,6 +120,41 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`,
|
||||
the caldav/carddav backends query the shares tables on every request
|
||||
(no caching), changes take effect immediately without restarting the
|
||||
server.
|
||||
- `internal/web` — the web UI, mounted at `/ui/` in `cmd/server/main.go`
|
||||
(`web.NewServer(cfg, st, dbase, logger).Handler(webstatic.FS())`),
|
||||
entirely separate from `internal/auth`'s Basic Auth: logins go through
|
||||
`/ui/login` (username/password checked against `cfg.Users` the same way
|
||||
Basic Auth does, via bcrypt) and issue an opaque random session token
|
||||
stored in the `web_sessions` SQLite table (`db.CreateSession`/
|
||||
`SessionUser`/`DeleteSession`, see `internal/db/sessions.go`), set as an
|
||||
`HttpOnly` cookie (`sessionCookieName` in `internal/web/session.go`).
|
||||
`requireLogin` is the auth-guard middleware for authenticated routes,
|
||||
storing the username in the request context (`userFromContext`).
|
||||
`internal/web/dashboard.go` renders the logged-in user's own
|
||||
calendars/address books plus who they're shared with
|
||||
(`SharesOfCalendar`/`SharesOfAddressBook`) and what's shared with them
|
||||
(`CalendarsSharedWith`/`AddressBooksSharedWith`). `internal/web/shares.go`
|
||||
handles POST (create/update share) and DELETE (revoke) at
|
||||
`/ui/shares/{calendar,addressbook}`, re-rendering just the affected
|
||||
resource card for htmx's `hx-swap="outerHTML"`; it always checks
|
||||
`ownsResource` first so a user can only share resources actually
|
||||
configured for their own account (never someone else's, even via a
|
||||
forged form post). **htmx v2 quirk**: `hx-delete` requests send
|
||||
`hx-vals`/form params as URL **query string** parameters, not a request
|
||||
body (unlike POST/PUT/PATCH) — `handleShare` special-cases
|
||||
`r.Method == http.MethodDelete` to read from `r.URL.Query()` instead of
|
||||
calling `r.ParseForm()`. Templates live in `internal/web/templates/*.templ`
|
||||
(compiled to `*_templ.go` via `templ generate`/`make templ-generate` —
|
||||
regenerate after editing any `.templ` file, the generated files are
|
||||
committed). Styling is Tailwind v4, scanned directly over the generated
|
||||
`_templ.go` files (`web/input.css`'s `@source` directives) and compiled
|
||||
to `web/static/app.css` via `make web-css` (needs Node/npm — see
|
||||
`web/package.json`); htmx itself is vendored as a static file
|
||||
(`web/static/htmx.min.js`, not npm-installed) to avoid a CDN dependency.
|
||||
Both static assets are embedded into the Go binary at build time via
|
||||
`web/staticassets.go` (`//go:embed static`), so the compiled server has
|
||||
no runtime dependency on Node.js or the `web/` directory being present —
|
||||
Node/npm are only needed when actually changing templates/styles.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user