# Watchlist CSV Export and Import Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add CSV export and import functionality to the anime watchlist to allow users to backup and restore their lists. **Architecture:** Client-side CSV generation for export using JavaScript Blobs. Server-side CSV parsing for import using a new API endpoint and Go's `encoding/csv` package. **Tech Stack:** Go (Backend), HTML/JavaScript (Frontend), Tailwind CSS (Styling). --- ### Task 1: Add Import Logic to Watchlist Service **Files:** - Modify: `api/watchlist/service.go` - [ ] **Step 1: Add ImportWatchlist method** Add a method to handle CSV parsing and entry upsertion. ```go func (s *Service) ImportWatchlist(ctx context.Context, userID string, r io.Reader) error { reader := csv.NewReader(r) // Read header if _, err := reader.Read(); err != nil { return fmt.Errorf("failed to read csv header: %w", err) } records, err := reader.ReadAll() if err != nil { return fmt.Errorf("failed to read csv records: %w", err) } for i, record := range records { if len(record) < 4 { continue // Skip malformed rows } animeID, err := strconv.ParseInt(record[0], 10, 64) if err != nil { return fmt.Errorf("row %d: invalid anime id: %w", i+1, err) } status := record[1] if _, ok := validStatuses[status]; !ok { status = "plan_to_watch" } currentEpisode, _ := strconv.ParseInt(record[2], 10, 64) currentTimeSeconds, _ := strconv.ParseFloat(record[3], 64) if err := s.ensureAnimeExists(ctx, animeID); err != nil { return fmt.Errorf("row %d: failed to ensure anime: %w", i+1, err) } _, err = s.db.UpsertWatchListEntry(ctx, db.UpsertWatchListEntryParams{ ID: uuid.New().String(), UserID: userID, AnimeID: animeID, Status: status, CurrentEpisode: sql.NullInt64{Int64: currentEpisode, Valid: currentEpisode > 0}, CurrentTimeSeconds: currentTimeSeconds, }) if err != nil { return fmt.Errorf("row %d: failed to upsert entry: %w", i+1, err) } } return nil } ``` - [ ] **Step 2: Commit** ```bash git add api/watchlist/service.go git commit -m "feat: add ImportWatchlist to service" ``` ### Task 2: Create Import API Endpoint **Files:** - Modify: `api/watchlist/handler.go` - Modify: `internal/server/routes.go` - [ ] **Step 1: Add HandleImportWatchlist to Handler** ```go func (h *Handler) HandleImportWatchlist(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } user := middleware.GetUser(r.Context()) if user == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } file, _, err := r.FormFile("file") if err != nil { http.Error(w, "failed to get file from request", http.StatusBadRequest) return } defer file.Close() if err := h.service.ImportWatchlist(r.Context(), user.ID, file); err != nil { log.Printf("import failed: %v", err) http.Error(w, "import failed: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("HX-Redirect", "/watchlist") w.WriteHeader(http.StatusOK) } ``` - [ ] **Step 2: Register route in internal/server/routes.go** ```go // Find existing watchlist routes and add: mux.HandleFunc("/api/watchlist/import", watchlistHandler.HandleImportWatchlist) ``` - [ ] **Step 3: Commit** ```bash git add api/watchlist/handler.go internal/server/routes.go git commit -m "feat: add watchlist import api endpoint" ``` ### Task 3: Update Frontend Templates with Export/Import UI **Files:** - Modify: `templates/watchlist.gohtml` - Modify: `templates/watchlist_partial.gohtml` - [ ] **Step 1: Add buttons and JS logic to templates** Add the UI components and scripts for CSV generation and file upload. ```html