Introduces a three-tier role hierarchy: reader < uploader < admin. - internal/users/role.go: Role type, roleLevel map, AtLeast(min), ParseRole() — new roles added by inserting into roleLevel only - internal/users/store.go: versioned migrations via _schema_version table; v2 migration adds 'role' column and migrates existing can_upload data; SetRole() replaces SetUpload(); Session carries Role instead of CanUpload - internal/web/middleware.go: generic requireRole(minRole) middleware replaces the ad-hoc requireUpload - internal/web/handlers.go: upload routes use requireRole(RoleUploader); listBooks derives canUpload from sess.Role.AtLeast(RoleUploader) - cmd/admin/main.go: user add --role <reader|uploader|admin>, user set-role replaces user set-upload - README, copilot-instructions updated Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3.7 KiB
3.7 KiB
eBook Library (Go + templ)
Minimalistic, no-JS eBook library web app for reading files (EPUB/PDF) on a Tolino e-reader browser. UI text/comments are in German.
Build & run
go mod tidy # install deps
templ generate # regenerate views/*_templ.go from views/*.templ (required after editing .templ files)
go build ./... # build check
go run ./cmd/server # run dev server on :8080
templCLI is required wheneverviews/pages.templchanges — generated Go code lives inviews/pages_templ.goand is checked into the repo, so it must be regenerated and committed together with template edits.- No test suite exists yet.
- Config via env vars:
ADDR(default:8080),BOOKS_DIR(defaultbooks),USERS_DB(defaultusers.db).
Admin CLI
go run ./cmd/admin user add <name> [--role reader|uploader|admin] # Standard: reader
go run ./cmd/admin user list
go run ./cmd/admin user delete <name>
go run ./cmd/admin user set-role <name> <reader|uploader|admin>
Architecture
cmd/server/main.go— entrypoint; wireslibrary.Service,users.Storeandweb.Handlertogether and starts the HTTP server.internal/library— core domain logic, no HTTP dependency (unchanged).internal/users/role.go—Roletype (reader,uploader,admin),roleLevelmap für Hierarchie,AtLeast(min Role),ParseRole(s). Neue Rollen: inroleLeveleintragen und ggf. Routen anpassen — kein Schema-Change nötig.internal/users/store.go— SQLite-backed user store. Migrations via_schema_version-Tabelle (geordnetemigrations [][]string); neue Migrationen am Ende anhängen.role TEXTstattcan_upload INTEGER.internal/web/middleware.go—requireRole(minRole users.Role)ist die zentrale Middleware;requireAuthist ein Spezialfall davon (implizitRoleReader).- Auth:
/loginund/static/sind die einzigen öffentlichen Routes. Upload-Routes verwendenrequireRole(RoleUploader).
Rollen-Hierarchie:
reader (0) < uploader (1) < admin (2)
cmd/admin/main.go— CLI binary for user management; readsUSERS_DBenv var.internal/web/handlers.go— HTTP handlers includingloginPage,loginSubmit,logout,uploadPage,uploadSubmit.internal/web/middleware.go—requireAuthandrequireUploadmiddleware; session resolved from"session"cookie; stored in request context viasessionKey.views/pages.templ—LoginPage(errMsg),UploadPage(errMsg)added;IndexPagenow takescanUpload bool;Layoutincludes logout button.
Conventions
- Path traversal guards matter:
downloadBookvalidates the resolved book path stays withinBooksDir()before serving;cleanEPUBPathrejects../-escaping hrefs inside EPUB zips;uploadSubmitvalidates the destination path stays withinBooksDir(). Preserve these checks in any related changes. - Cover image reads are capped (
maxCoverBytes = 10 MiB) to avoid decompression abuse — keep similar limits when reading zip entries. - Handlers return
404for missing/emptyidand500on internal errors; keep that pattern for new routes. - Auth:
/loginand/static/are the only public routes — everything else goes throughrequireAuth. Routes needing upload permission userequireUpload(which wrapsrequireAuth). - Session cookie:
HttpOnly,SameSite=Lax, 30-day expiry; noSecureflag set (intended for LAN use without TLS). modernc.org/sqliteis a pure-Go SQLite driver (no CGo).MaxOpenConns(1)is set because SQLite doesn't support concurrent writers.users.dbis gitignored; create it at runtime with the admin CLI.