diff options
| author | ivar <i@oiee.no> | 2026-04-03 14:22:23 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-03 14:22:23 +0200 |
| commit | 33310be68544d3381ac6e9899790f4a106e17e8f (patch) | |
| tree | 385102a7216ffba17d054f11032dae4447371d52 /docs/superpowers/specs | |
| parent | a8914a8f18c345e934bce93b37845a9dfe0ad73e (diff) | |
| download | nebbet.no-33310be68544d3381ac6e9899790f4a106e17e8f.tar.xz nebbet.no-33310be68544d3381ac6e9899790f4a106e17e8f.zip | |
refactor: convert admin handlers to Gin context-based signatures
- Remove old ServeHTTP method (no longer needed with Gin routing)
- Update all 6 handler methods to use *gin.Context instead of http.ResponseWriter, *http.Request
- Convert handler signatures: handleList, handleNew, handleNewPost, handleEdit, handleDelete
- Remove render() helper (use c.HTML() directly)
- Update renderError() to accept gin.Context instead of http.ResponseWriter
- Update postFromForm() to extract form data from gin.Context using c.PostForm()
- Update main.go to use adminSrv.NewServer() and adminSrv.Engine()
- All handlers now use Gin methods: c.HTML(), c.PostForm(), c.Param(), c.Redirect()
- Path parameters now extracted via c.Param("slug") instead of function arguments
- HTTP status codes and error handling fully migrated to Gin patterns
Build verified: go build ./cmd/nebbet succeeds
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs')
| -rw-r--r-- | docs/superpowers/specs/2026-04-03-gin-migration-design.md | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-04-03-gin-migration-design.md b/docs/superpowers/specs/2026-04-03-gin-migration-design.md new file mode 100644 index 0000000..41163a3 --- /dev/null +++ b/docs/superpowers/specs/2026-04-03-gin-migration-design.md @@ -0,0 +1,198 @@ +--- +name: Gin Migration Design +description: Migrate admin server from net/http to Gin framework with RESTful routes and middleware-based auth +type: implementation +date: 2026-04-03 +--- + +# Gin Migration Design + +## Overview + +Migrate the nebbet admin server from Go's `net/http` package to the Gin web framework. The migration will: +- Replace manual route handling with Gin's declarative routing +- Reorganize routes to follow RESTful conventions +- Extract auth into reusable middleware +- Preserve all existing functionality (auth, template rendering, post management) +- Keep UI behavior identical from the user's perspective + +## Goals + +1. **Cleaner routing:** Replace manual `ServeHTTP` switch with Gin's route declarations +2. **Better framework:** Leverage Gin for middleware, error handling, and future extensibility +3. **RESTful design:** Modernize route structure while keeping templates server-rendered + +## Non-Goals + +- Rewriting the frontend to a JS framework +- Adding new features beyond the migration +- Changing auth format or password file structure +- Changing how posts are stored or built + +## Architecture + +### Current State + +The `admin.Server` implements `http.Handler` with manual routing in `ServeHTTP()`: +``` +GET/POST /admin → list posts +GET/POST /admin/new → create form / create post +GET/POST /admin/{slug}/edit → edit form / update post +POST /admin/{slug}/delete → delete post +``` + +Auth is checked manually via `checkAuth()` for every request. + +### Target State + +Replace with Gin routing and middleware: +- `Server` struct holds a `*gin.Engine` +- `NewServer()` initializes Gin with routes and middleware +- Auth middleware wraps all admin routes +- Handlers call `gin.Context` instead of `http.ResponseWriter` + +**New route structure (RESTful, under `/admin` namespace):** +``` +GET /admin/ → list posts +GET /admin/new → create form +POST /admin/ → create post +GET /admin/:slug → edit form +POST /admin/:slug → update post +DELETE /admin/:slug → delete post +``` + +## Implementation Details + +### File: `internal/admin/server.go` + +#### Server Struct +- Add field: `engine *gin.Engine` +- Keep existing fields: `PostsDir`, `AuthFile`, `Builder`, `tmpl` + +#### Constructor: `NewServer(postsDir, authFile string, builder *builder.Builder) *Server` +- Create and configure Gin engine +- Register middleware (auth) +- Register all routes +- Return `*Server` + +#### Auth Middleware +- Extracted from `checkAuth()` into a middleware function +- Signature: `func (s *Server) authMiddleware() gin.HandlerFunc` +- Logic: + - Skip auth if `AuthFile` is empty or doesn't exist + - Extract Basic Auth credentials + - Call `auth.Verify(username, password)` + - Send 401 + `WWW-Authenticate` header on failure + - Call `c.Next()` to proceed on success + +#### Route Handlers +Keep existing handler functions, update signatures: +- `handleList(c *gin.Context)` — render post list +- `handleNew(c *gin.Context)` — GET shows form, POST creates post +- `handleNewPost(c *gin.Context)` — handle POST /new (merge with `handleNew` or keep separate) +- `handleEdit(c *gin.Context)` — GET shows form, POST updates post +- `handleDelete(c *gin.Context)` — DELETE removes post + +For GET requests that show forms, continue using `c.HTML()` with the template and data map. +For POST requests, validate form data, write to disk, rebuild, and redirect to `/`. + +#### Helper Functions +Keep existing: +- `listPosts()` — read `.md` files from PostsDir +- `render(c *gin.Context, name string, data map[string]any)` — render template (adapt to use `c.HTML()`) +- `renderError(c *gin.Context, msg string)` — render error template +- `postFromForm(c *gin.Context) Post` — extract form values from `c.PostForm()` +- `readPostFile()`, `writePostFile()`, `slugify()` — unchanged +- `mustParseTemplates()` — unchanged + +### File: `cmd/nebbet/main.go` + +#### Changes in `cmdServe()` +Instead of: +```go +adminSrv := &admin.Server{...} +mux := http.NewServeMux() +mux.Handle("/admin/", http.StripPrefix("/admin", adminSrv)) +mux.Handle("/lib/", ...) +mux.Handle("/", ...) +http.ListenAndServe(addr, mux) +``` + +Change to: +```go +adminSrv := admin.NewServer(postsDir, passwordFile, b) +engine := adminSrv.Engine() // or keep internal, add to main router + +// Either: +// 1. Use adminSrv.Engine directly (if it handles all routes) +// 2. Create a main Gin router and nest adminSrv's routes + +// Handle /lib/ and static files as Gin routes or separate handlers +``` + +Exact approach depends on whether we keep `/lib` and public site serving separate or consolidate into one Gin router. **Recommended:** single Gin router for consistency. + +## Route Mapping + +| Old Route | New Route | Method | Action | +|-----------|-----------|--------|--------| +| `/admin/` | `/admin/` | GET | List posts | +| `/admin/new` | `/admin/new` | GET | Show create form | +| `/admin/new` | `/admin/` | POST | Create post | +| `/admin/{slug}/edit` | `/admin/{slug}` | GET | Show edit form | +| `/admin/{slug}/edit` | `/admin/{slug}` | POST | Update post | +| `/admin/{slug}/delete` | `/admin/{slug}` | DELETE | Delete post | + +**HTML Form Handling:** Standard HTML forms only support GET/POST. For DELETE: +- **Option A:** Keep POST with custom handling (check method override or hidden field) +- **Option B:** Use POST `/admin/{slug}?method=delete` and parse in handler + +Recommended: keep as POST for form compatibility, or use POST with Gin's `c.PostForm("_method")` check. + +## Dependencies + +- Add `github.com/gin-gonic/gin` to `go.mod` +- No changes to other dependencies + +## Backwards Compatibility + +- **URLs:** Admin routes change (listed above). Bookmarks will break, but it's an internal admin interface. +- **Auth:** No changes. Password file format and Basic Auth remain the same. +- **Posts:** No changes. Markdown files, frontmatter, build process unchanged. +- **Public site:** No changes. Static files served the same way. + +## Testing Strategy + +- **Manual:** Create/edit/delete posts via the admin UI, verify rebuilds work +- **Auth:** Test Basic Auth with valid/invalid credentials +- **Forms:** Verify all form fields (title, date, tags, content) are captured and saved correctly +- **Errors:** Verify error handling for missing posts, invalid input, file write failures + +## Implementation Order + +1. Add Gin dependency to `go.mod` +2. Create `NewServer()` constructor and auth middleware in `server.go` +3. Register routes in `NewServer()` +4. Convert handler signatures to `*gin.Context` +5. Update `render()` and helper functions to work with Gin +6. Update `cmd/nebbet/main.go` to use `NewServer()` +7. Verify all routes work locally +8. Test admin UI end-to-end + +## Risks & Mitigation + +| Risk | Mitigation | +|------|-----------| +| Breaking the admin UI during migration | Test each route in a browser after updating | +| Form submission issues (e.g., multipart/form-data) | Use `c.PostForm()` and test file uploads if used | +| Auth middleware interfering with static files | Apply middleware only to admin routes, not `/lib` or `/` | +| Template rendering errors | Keep template loading and execution the same | + +## Success Criteria + +- ✓ All admin routes work with Gin +- ✓ Auth middleware blocks unauthorized access +- ✓ Create/edit/delete posts works end-to-end +- ✓ Posts rebuild after create/edit/delete +- ✓ No changes to password file format or post storage +- ✓ Static file serving (`/lib`, public site) unchanged |
