diff --git a/ECHO_V5_MIGRATION.md b/ECHO_V5_MIGRATION.md deleted file mode 100644 index 92e7ab2..0000000 --- a/ECHO_V5_MIGRATION.md +++ /dev/null @@ -1,338 +0,0 @@ -# Echo v5 Migration Plan - -## Overview - -This document outlines the steps required to migrate from Echo v4 to Echo v5, including API changes, type signature updates, and middleware modifications. - -## Errors Identified - -### 1. Deprecated Middleware - -**Error:** `echomiddleware.Logger undefined` - -**Solution:** Replace `echomiddleware.Logger()` with `echomiddleware.RequestLogger()` - -**Files affected:** -- `cmd/server/main.go` (line 142) -- `internal/router/router.go` (line 144) - -### 2. Removed API Methods - -**Error:** `a.echo.Close undefined (type *echo.Echo has no field or method Close)` - -**Solution:** Remove the `echo.Close()` call as v5 uses different graceful shutdown mechanism - -**Files affected:** -- `internal/app/app.go` (line 86) - -### 3. Type Signature & Response API Changes - -**Error:** Multiple type mismatches and Response API changes in Echo v5 - -**Root Cause:** Echo v5 uses `*echo.Context` (pointer) for handlers but some code uses `echo.Context` (value). Response wrapper API also changed. - -**Solution:** -- Update all middleware to use `*echo.Context` -- Update helper functions to accept `*echo.Context` -- Fix Response wrapper usage for Echo v5 API - -**Files affected:** -- `internal/middleware/device_auth.go` (lines 38, 170, 212) -- `internal/middleware/error_handler.go` (lines 44, 69, 82, 84, 87, 89) -- `internal/middleware/rate_limiter.go` (line 102) -- `internal/middleware/request_tracing.go` (lines 48, 58, 60) -- `internal/middleware/security.go` (line 14) - ---- - -## Step-by-Step Fixes - -### Step 1: Fix Deprecated Logger Middleware - -#### File: `cmd/server/main.go` - -**Line 142:** -```go -// BEFORE: -e.Use(echomiddleware.Logger()) - -// AFTER: -e.Use(echomiddleware.RequestLogger()) -``` - -#### File: `internal/router/router.go` - -**Line ~144:** -```go -// BEFORE: -e.Use(echomiddleware.Logger()) - -// AFTER: -e.Use(echomiddleware.RequestLogger()) -``` - ---- - -### Step 2: Fix Removed API Methods - -#### File: `internal/app/app.go` - -**Lines 26-32 - Initialize server field:** -```go -// REPLACE lines 26-32 with: -func New(echo *echo.Echo) *App { - return &App{ - echo: echo, - server: nil, // Will be set in StartServer() - shutdownTimeout: 30 * time.Second, - shutdownDone: make(chan struct{}), - } -} -``` - -**Add StartServer method after New() (after line 32):** -```go -// StartServer creates HTTP server and starts listening -func (a *App) StartServer(addr string) error { - a.server = &http.Server{ - Addr: addr, - Handler: a.echo, - } - - // Start HTTP server in background - go func() { - if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Server failed to start: %v", err) - } - }() - - return nil -} -``` - -**Lines 82-94 - Replace goroutine in Shutdown():** -```go -// REPLACE lines 82-94 with: - // Stop accepting new connections and shutdown HTTP server - log.Println("Stopping HTTP server...") - if a.server != nil { - ctx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout) - defer cancel() - - if err := a.server.Shutdown(ctx); err != nil { - log.Printf("Error stopping HTTP server: %v", err) - } - } - - log.Println("All services stopped") -``` - -#### File: `cmd/server/main.go` - -**Lines 197-214 - Replace server startup:** -```go -// REPLACE lines 197-214 with: - // ======================================================================== - // START SERVER (managed by app lifecycle) - // ======================================================================== - - log.Printf("Starting server on port %s", cfg.ServerPort) - - // Start HTTP server - if err := application.StartServer(":" + cfg.ServerPort); err != nil { - log.Fatalf("Failed to start server: %v", err) - } - - // Start application lifecycle (blocks until shutdown signal) - if err := application.Start(); err != nil { - log.Fatalf("Application error: %v", err) - } -``` - ---- - -### Step 3: Fix Middleware Type Signatures - -#### Pattern for Echo v5 Middleware - -**Echo v5 Middleware Pattern:** -```go -func MiddlewareFunc(next echo.HandlerFunc) echo.HandlerFunc { - return func(c *echo.Context) error { // ← POINTER (required in v5) - // ... middleware logic - return next(c) // ← c is already a pointer, no & needed - } -} -``` - -#### File: `internal/middleware/device_auth.go` - -**Line 38:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - -**Line 170:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - -**Line 212:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - -#### File: `internal/middleware/error_handler.go` - -**Line 44:** -```go -// BEFORE: -func RespondWithError(c echo.Context, code int, message string, err error) error { - -// AFTER: -func RespondWithError(c *echo.Context, code int, message string, err error) error { -``` - -**Line 69:** -```go -// BEFORE: -func RespondWithHTTPError(c echo.Context, httpErr *HTTPError) error { - -// AFTER: -func RespondWithHTTPError(c *echo.Context, httpErr *HTTPError) error { -``` - -**Line 82:** -```go -// BEFORE: -func WrapHandler(fn func(c echo.Context) error) echo.HandlerFunc { - return func(c *echo.Context) error { - err := fn(c) - -// AFTER: -func WrapHandler(fn func(*echo.Context) error) echo.HandlerFunc { - return func(c *echo.Context) error { - err := fn(c) // c is already *echo.Context -``` - -**Lines 84, 87, 89:** No change - calls to helpers will now work with updated signatures - -#### File: `internal/middleware/rate_limiter.go` - -**Line 102:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - -#### File: `internal/middleware/request_tracing.go` - -**Line 48:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - -**Lines 57-61:** -```go -// BEFORE: -recorder := &responseWriter{ - ResponseWriter: c.Response().Writer, -} -c.Response().Writer = recorder - -// AFTER: -recorder := &responseWriter{ - ResponseWriter: *c.Response(), // Dereference: *echo.Response -> http.ResponseWriter -} -``` - -**Note:** `c.Response().Status` (line 108) should work - Status field exists in Echo v5 - -#### File: `internal/middleware/security.go` - -**Line 14:** -```go -// BEFORE: -return func(c echo.Context) error { - -// AFTER: -return func(c *echo.Context) error { -``` - ---- - -## WebSocket Fix - -After Echo v5 migration, the WebSocket handler should now work natively: - -### File: `internal/handlers/websocket.go` - -The existing code should now work: -```go -ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) -``` - -This previously failed with "response does not implement http.Hijacker" but Echo v5's Response now supports the `rwUnwrapper` interface needed for WebSocket upgrades. - ---- - -## Verification - -After making all changes: - -1. **Build the project:** - ```bash - go build ./cmd/server - ``` - -2. **Run tests:** - ```bash - go test ./cmd/server/tests/... -v - ``` - -3. **Test WebSocket manually:** - - Get JWT token: `curl -X POST http://localhost:8765/api/auth/login -H "Content-Type: application/json" -d '{"login":"testuser@tests.bookhoard.internal","password":"Test@Pass123!"}' | jq -r '.access_token'` - - Connect in browser console: `new WebSocket('ws://localhost:8765/ws/sync?token=YOUR_TOKEN')` - ---- - -## Rollback Plan - -If Echo v5 migration fails: - -1. Revert go.mod changes: - ```go - // Change back to v4: - github.com/labstack/echo/v4 v4.15.1 - github.com/labstack/echo-jwt/v4 v4.4.0 - ``` - -2. Restore import statements in all modified files -3. Revert all code changes - ---- - -## References - -- [Echo v5 Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md) -- [Echo v5 Middleware Documentation](https://echo.labstack.com/docs/middleware) -- [Echo WebSocket Cookbook](https://echo.labstack.com/docs/cookbook/websocket) diff --git a/esbuild-setup.md b/esbuild-setup.md deleted file mode 100644 index 3f6ad61..0000000 --- a/esbuild-setup.md +++ /dev/null @@ -1,1953 +0,0 @@ -# ESBuild Setup Guide - -## Overview -Migrate to a single bundled `main.js` using ESBuild for better performance, simplified deployment, and broader browser compatibility (ES2020 target = Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+). - -**Target Bundle Size**: ~100KB minified + gzip -**Browser Support**: ES2020 (Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+, Ubuntu 22.04 LTS browsers) -**Architecture**: SSR-first, procedural TypeScript, progressive enhancement (per PROJECT_GUIDELINES.md) - ---- - -## Status: What's Already Done ✅ - -### 1. package.json Dependencies (Already Correct) -**File**: `package.json` -**Status**: ✅ Already configured - NO CHANGES NEEDED - -Dependencies (lines 14-26): -- `htmx.org`, `alpinejs`, `lunr`, `highlight.js` already in dependencies -- `esbuild`, `typescript`, TailwindCSS tooling already in devDependencies - -### 2. Build Scripts (Already Correct) -**File**: `package.json` -**Status**: ✅ Already configured - NO CHANGES NEEDED - -Lines 8-10: -```json -"build:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify", -"watch:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020", -"dev": "concurrently \"npm run watch:css\" \"npm run watch:ts\" \"npm run watch:templ\"", -``` - -Already using `--target=es2020` for broad browser compatibility. - -### 3. Entry Point (Already Exists) -**File**: `web/src/main.ts` -**Status**: ✅ Already exists - NO CHANGES NEEDED - -Already imports all 29 modules correctly (24 lines). Main.ts stays simple - imports happen in individual files where used. - ---- - -## Phase 1: Clean Up docs.ts - -**File**: `web/src/docs.ts` (99 lines) -**Status**: Imports already added ✅ -**Lines to modify**: 50, 56, 73 (remove window globals), 99 (replace with Alpine) - -**Note**: Lines 3-4 already have the correct imports: -```typescript -import * as lunr from "lunr"; -import hljs from "highlight.js"; -``` - -### Step 1: Remove (window as any).lunr check -**Location**: Line 50 - -**Current** (lines 49-53): -```typescript - if (!(window as any).lunr) { - console.warn("Lunr.js not loaded"); - return; - } -``` - -**Change to**: -```typescript - // Lunr now bundled via ESBuild -``` - -### Step 2: Replace (window as any).lunrIndex with direct lunr usage -**Location**: Line 56 - -**Current** (lines 55-62): -```typescript - const idx = (window as any).lunrIndex; - if (!idx) { - searchResults.innerHTML = - '
Search index not loaded
'; - searchResults.classList.remove("hidden"); - return; - } -``` - -**Change to**: -```typescript - const idx = lunr.Builder.loadJs(searchIndex); - if (!idx) { - searchResults.innerHTML = - 'Search index not loaded
'; - searchResults.classList.remove("hidden"); - return; - } -``` - -### Step 3: Replace (window as any).docsData with direct import -**Location**: Line 73 - -**Current** (lines 70-76): -```typescript - .map((result: { ref: string }) => { - const doc = (window as any).docsData?.[result.ref]; - if (!doc) return ""; -``` - -**Change to**: -```typescript - .map((result: { ref: string }) => { - const doc = docs[result.ref]; - if (!doc) return ""; -``` - -### Step 4: Replace window export with Alpine global -**Location**: Line 99 - -**Current** (lines 98-99): -```typescript -document.addEventListener("DOMContentLoaded", () => { - initializeDocsSearch(); -}); - -(window as any).toggleSidebar = toggleSidebar; -``` - -**Change to**: -```typescript -document.addEventListener("DOMContentLoaded", () => { - initializeDocsSearch(); - - // Register with Alpine globally - if (typeof window.Alpine !== 'undefined') { - window.Alpine.effect(() => { - window.Alpine.global('docs', { - toggleSidebar - }); - }); - } -}); -``` - -**Why Alpine.global()**: Makes `toggleSidebar()` available to Alpine templates via `@click="docs.toggleSidebar()"` - ---- - -## Phase 2: Migrate TypeScript Files to Alpine Registration - -**Approach**: Replace `(window as any)` exports with Alpine.js global registration - -### Architecture Note: Alpine.js for Client-Side State -**Why Alpine over window exports**: -- Modern, reactive framework (already in package.json) -- Clean template syntax: `@click` instead of `onclick="window.func()"` -- Built-in state management: `x-data`, `x-show`, `x-model` -- Works with SSR (progressive enhancement) -- No global namespace pollution - -**Hybrid approach**: -- **Alpine**: Client-side state (modals, dropdowns, theme, forms) -- **HTMX**: Server calls (already using for form submissions) - -### Step 1: Create Alpine Registration Helper - -**New file**: `web/src/alpine.ts` - -```typescript -import Alpine from 'alpinejs'; - -// Initialize Alpine -window.Alpine = Alpine; -Alpine.start(); - -// Re-export Alpine for other modules to use -export { Alpine }; -``` - -**Add to main.ts**: Append this line at the end of `web/src/main.ts`: - -```typescript -import './alpine'; -``` - -### Step 2: Update TypeScript Files to Register with Alpine - -#### Pattern: Object Registration (Multiple Related Functions) - -**Example**: `web/src/toast.ts` - -**Current** (lines 229-236): -```typescript -(window as any).showToast = { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}; -``` - -**Change to**: -```typescript -import { Alpine } from './alpine'; - -// Register toast API with Alpine -Alpine.global('showToast', { - error: (message: string, duration?: number) => - showToast(message, "error", duration), - success: (message: string, duration?: number) => - showToast(message, "success", duration), - info: (message: string, duration?: number) => - showToast(message, "info", duration), -}); -``` - -**Usage in templates**: -```html - - - - - - - -``` - -#### Pattern: Namespace Registration (Grouping Related Functions) - -**Example**: `web/src/api.ts` - -**Current** (lines 90-99): -```typescript -(window as any).api = { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}; -``` - -**Change to**: -```typescript -import { Alpine } from './alpine'; - -Alpine.global('api', { - get: apiGet, - post: apiPost, - put: apiPut, - delete: apiDelete, - patch: apiPatch, - handleResponse, - handleVoidResponse, - handleError, -}); -``` - -**Usage in templates**: -```html - - - - - -``` - -#### Pattern: Stateful Components (Dropdowns, Modals) - -**Example**: `web/src/header.ts` (theme dropdown) - -**Current approach**: Multiple window exports - -**New approach**: Create Alpine component with state - -**Add to `web/src/header.ts`**: -```typescript -import { Alpine } from './alpine'; - -// Register theme dropdown component -Alpine.data('themeDropdown', () => ({ - open: false, - - toggle() { - this.open = !this.open; - }, - - changeTheme(theme: string) { - changeThemeTo(theme); // Reuse existing function - this.open = false; - }, - - init() { - // Load saved theme on init - applyTheme(loadTheme()); - } -})); -``` - -**Usage in templates**: -```html - - -