diff options
Diffstat (limited to 'docs/superpowers/specs/2026-04-03-gin-migration-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-04-03-gin-migration-design.md | 198 |
1 files changed, 0 insertions, 198 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 deleted file mode 100644 index 41163a3..0000000 --- a/docs/superpowers/specs/2026-04-03-gin-migration-design.md +++ /dev/null @@ -1,198 +0,0 @@ ---- -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 |
